Fix compiler warning due to missing function prototype.
[svn.git] / subversion / libsvn_fs_fs / fs_fs.c
blobb59ef0613299dfc8b2b3028614cede66a2ef0bce
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;
1023 /* Wrapper around svn_io_file_create which ignores EEXIST. */
1024 static svn_error_t *
1025 create_file_ignore_eexist(const char *file,
1026 const char *contents,
1027 apr_pool_t *pool)
1029 svn_error_t *err = svn_io_file_create(file, contents, pool);
1030 if (err && APR_STATUS_IS_EEXIST(err->apr_err))
1032 svn_error_clear(err);
1033 err = SVN_NO_ERROR;
1035 return err;
1038 static svn_error_t *
1039 upgrade_body(void *baton, apr_pool_t *pool)
1041 svn_fs_t *fs = baton;
1042 int format, max_files_per_dir;
1043 const char *format_path = path_format(fs, pool);
1045 /* Read the FS format number and max-files-per-dir setting. */
1046 SVN_ERR(read_format(&format, &max_files_per_dir, format_path, pool));
1048 /* If we're already up-to-date, there's nothing to be done here. */
1049 if (format == SVN_FS_FS__FORMAT_NUMBER)
1050 return SVN_NO_ERROR;
1052 /* If our filesystem predates the existance of the 'txn-current
1053 file', make that file and its corresponding lock file. */
1054 if (format < SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
1056 SVN_ERR(create_file_ignore_eexist(path_txn_current(fs, pool), "0\n",
1057 pool));
1058 SVN_ERR(create_file_ignore_eexist(path_txn_current_lock(fs, pool), "",
1059 pool));
1062 /* If our filesystem predates the existance of the 'txn-protorevs'
1063 dir, make that directory. */
1064 if (format < SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
1066 /* We don't use path_txn_proto_rev() here because it expects
1067 we've already bumped our format. */
1068 SVN_ERR(svn_io_make_dir_recursively
1069 (svn_path_join(fs->path, PATH_TXN_PROTOS_DIR, pool), pool));
1072 /* Bump the format file. We pass 0 for the max_files_per_dir here
1073 so we don't have to fuss with sharding directories ourselves. */
1074 SVN_ERR(write_format(format_path, SVN_FS_FS__FORMAT_NUMBER, 0,
1075 TRUE, pool));
1077 return SVN_NO_ERROR;
1081 svn_error_t *
1082 svn_fs_fs__upgrade(svn_fs_t *fs, apr_pool_t *pool)
1084 return svn_fs_fs__with_write_lock(fs, upgrade_body, (void *)fs, pool);
1088 /* SVN_ERR-like macros for dealing with recoverable errors on mutable files
1090 * Revprops, current, and txn-current files are mutable; that is, they
1091 * change as part of normal fsfs operation, in constrat to revs files, or
1092 * the format file, which are written once at create (or upgrade) time.
1093 * When more than one host writes to the same repository, we will
1094 * sometimes see these recoverable errors when accesssing these files.
1096 * These errors all relate to NFS, and thus we only use this retry code if
1097 * ESTALE is defined.
1099 ** ESTALE
1101 * In NFS v3 and under, the server doesn't track opened files. If you
1102 * unlink(2) or rename(2) a file held open by another process *on the
1103 * same host*, that host's kernel typically renames the file to
1104 * .nfsXXXX and automatically deletes that when it's no longer open,
1105 * but this behavior is not required.
1107 * For obvious reasons, this does not work *across hosts*. No one
1108 * knows about the opened file; not the server, and not the deleting
1109 * client. So the file vanishes, and the reader gets stale NFS file
1110 * handle.
1112 ** EIO, ENOENT
1114 * Some client implementations (at least the 2.6.18.5 kernel that ships
1115 * with Ubuntu Dapper) sometimes give spurious ENOENT (only on open) or
1116 * even EIO errors when trying to read these files that have been renamed
1117 * over on some other host.
1119 ** Solution
1121 * Wrap opens and reads of such files with RETRY_RECOVERABLE and
1122 * closes with IGNORE_RECOVERABLE. Call these macros within a loop of
1123 * RECOVERABLE_RETRY_COUNT iterations (though, realistically, the
1124 * second try will succeed). Make sure you put a break statement
1125 * after the close, at the end of your loop. Immediately after your
1126 * loop, return err if err.
1128 * You must initialize err to SVN_NO_ERROR and filehandle to NULL, as
1129 * these macros do not.
1132 #define RECOVERABLE_RETRY_COUNT 10
1134 #ifdef ESTALE
1135 #define RETRY_RECOVERABLE(err, filehandle, expr) \
1137 svn_error_clear(err); \
1138 err = (expr); \
1139 if (err) \
1141 apr_status_t _e = APR_TO_OS_ERROR(err->apr_err); \
1142 if ((_e == ESTALE) || (_e == EIO) || (_e == ENOENT)) { \
1143 if (NULL != filehandle) \
1144 (void)apr_file_close(filehandle); \
1145 continue; \
1147 return err; \
1150 #define IGNORE_RECOVERABLE(err, expr) \
1152 svn_error_clear(err); \
1153 err = (expr); \
1154 if (err) \
1156 apr_status_t _e = APR_TO_OS_ERROR(err->apr_err); \
1157 if ((_e != ESTALE) && (_e != EIO)) \
1158 return err; \
1161 #else
1162 #define RETRY_RECOVERABLE(err, filehandle, expr) SVN_ERR(expr)
1163 #define IGNORE_RECOVERABLE(err, expr) SVN_ERR(expr)
1164 #endif
1166 /* Long enough to hold: "<svn_revnum_t> <node id> <copy id>\0"
1167 * 19 bytes for svn_revnum_t (room for 32 or 64 bit values)
1168 * + 2 spaces
1169 * + 26 bytes for each id (these are actually unbounded, so we just
1170 * have to pick something; 2^64 is 13 bytes in base-36)
1171 * + 1 terminating null
1173 #define CURRENT_BUF_LEN 48
1175 /* Read the 'current' file FNAME and store the contents in *BUF.
1176 Allocations are performed in POOL. */
1177 static svn_error_t *
1178 read_current(const char *fname, char **buf, apr_pool_t *pool)
1180 apr_file_t *revision_file = NULL;
1181 apr_size_t len;
1182 int i;
1183 svn_error_t *err = SVN_NO_ERROR;
1184 apr_pool_t *iterpool;
1186 *buf = apr_palloc(pool, CURRENT_BUF_LEN);
1187 iterpool = svn_pool_create(pool);
1188 for (i = 0; i < RECOVERABLE_RETRY_COUNT; i++)
1190 svn_pool_clear(iterpool);
1192 RETRY_RECOVERABLE(err, revision_file,
1193 svn_io_file_open(&revision_file, fname,
1194 APR_READ | APR_BUFFERED,
1195 APR_OS_DEFAULT, iterpool));
1197 len = CURRENT_BUF_LEN;
1198 RETRY_RECOVERABLE(err, revision_file,
1199 svn_io_read_length_line(revision_file,
1200 *buf, &len, iterpool));
1201 IGNORE_RECOVERABLE(err, svn_io_file_close(revision_file, iterpool));
1203 break;
1205 svn_pool_destroy(iterpool);
1207 return err;
1210 /* Find the youngest revision in a repository at path FS_PATH and
1211 return it in *YOUNGEST_P. Perform temporary allocations in
1212 POOL. */
1213 static svn_error_t *
1214 get_youngest(svn_revnum_t *youngest_p,
1215 const char *fs_path,
1216 apr_pool_t *pool)
1218 char *buf;
1220 SVN_ERR(read_current(svn_path_join(fs_path, PATH_CURRENT, pool),
1221 &buf, pool));
1223 *youngest_p = SVN_STR_TO_REV(buf);
1225 return SVN_NO_ERROR;
1228 svn_error_t *
1229 svn_fs_fs__hotcopy(const char *src_path,
1230 const char *dst_path,
1231 apr_pool_t *pool)
1233 const char *src_subdir, *dst_subdir;
1234 svn_revnum_t youngest, rev;
1235 apr_pool_t *iterpool;
1236 svn_node_kind_t kind;
1237 int format, max_files_per_dir;
1239 /* Check format to be sure we know how to hotcopy this FS. */
1240 SVN_ERR(read_format(&format, &max_files_per_dir,
1241 svn_path_join(src_path, PATH_FORMAT, pool),
1242 pool));
1243 SVN_ERR(check_format(format));
1245 /* Copy the current file. */
1246 SVN_ERR(svn_io_dir_file_copy(src_path, dst_path, PATH_CURRENT, pool));
1248 /* Copy the uuid. */
1249 SVN_ERR(svn_io_dir_file_copy(src_path, dst_path, PATH_UUID, pool));
1251 /* Find the youngest revision from this current file. */
1252 SVN_ERR(get_youngest(&youngest, dst_path, pool));
1254 /* Copy the necessary rev files. */
1255 src_subdir = svn_path_join(src_path, PATH_REVS_DIR, pool);
1256 dst_subdir = svn_path_join(dst_path, PATH_REVS_DIR, pool);
1258 SVN_ERR(svn_io_make_dir_recursively(dst_subdir, pool));
1260 iterpool = svn_pool_create(pool);
1261 for (rev = 0; rev <= youngest; rev++)
1263 const char *src_subdir_shard = src_subdir,
1264 *dst_subdir_shard = dst_subdir;
1266 if (max_files_per_dir)
1268 const char *shard = apr_psprintf(iterpool, "%ld",
1269 rev / max_files_per_dir);
1270 src_subdir_shard = svn_path_join(src_subdir, shard, iterpool);
1271 dst_subdir_shard = svn_path_join(dst_subdir, shard, iterpool);
1273 if (rev % max_files_per_dir == 0)
1274 SVN_ERR(svn_io_dir_make(dst_subdir_shard, APR_OS_DEFAULT,
1275 iterpool));
1278 SVN_ERR(svn_io_dir_file_copy(src_subdir_shard, dst_subdir_shard,
1279 apr_psprintf(iterpool, "%ld", rev),
1280 iterpool));
1281 svn_pool_clear(iterpool);
1284 /* Copy the necessary revprop files. */
1285 src_subdir = svn_path_join(src_path, PATH_REVPROPS_DIR, pool);
1286 dst_subdir = svn_path_join(dst_path, PATH_REVPROPS_DIR, pool);
1288 SVN_ERR(svn_io_make_dir_recursively(dst_subdir, pool));
1290 for (rev = 0; rev <= youngest; rev++)
1292 const char *src_subdir_shard = src_subdir,
1293 *dst_subdir_shard = dst_subdir;
1295 svn_pool_clear(iterpool);
1297 if (max_files_per_dir)
1299 const char *shard = apr_psprintf(iterpool, "%ld",
1300 rev / max_files_per_dir);
1301 src_subdir_shard = svn_path_join(src_subdir, shard, iterpool);
1302 dst_subdir_shard = svn_path_join(dst_subdir, shard, iterpool);
1304 if (rev % max_files_per_dir == 0)
1305 SVN_ERR(svn_io_dir_make(dst_subdir_shard, APR_OS_DEFAULT,
1306 iterpool));
1309 SVN_ERR(svn_io_dir_file_copy(src_subdir_shard, dst_subdir_shard,
1310 apr_psprintf(iterpool, "%ld", rev),
1311 iterpool));
1314 svn_pool_destroy(iterpool);
1316 /* Make an empty transactions directory for now. Eventually some
1317 method of copying in progress transactions will need to be
1318 developed.*/
1319 dst_subdir = svn_path_join(dst_path, PATH_TXNS_DIR, pool);
1320 SVN_ERR(svn_io_make_dir_recursively(dst_subdir, pool));
1321 if (format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
1323 dst_subdir = svn_path_join(dst_path, PATH_TXN_PROTOS_DIR, pool);
1324 SVN_ERR(svn_io_make_dir_recursively(dst_subdir, pool));
1327 /* Now copy the locks tree. */
1328 src_subdir = svn_path_join(src_path, PATH_LOCKS_DIR, pool);
1329 SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
1330 if (kind == svn_node_dir)
1331 SVN_ERR(svn_io_copy_dir_recursively(src_subdir, dst_path,
1332 PATH_LOCKS_DIR, TRUE, NULL,
1333 NULL, pool));
1335 /* Now copy the node-origins cache tree. */
1336 src_subdir = svn_path_join(src_path, PATH_NODE_ORIGINS_DIR, pool);
1337 SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
1338 if (kind == svn_node_dir)
1339 SVN_ERR(svn_io_copy_dir_recursively(src_subdir, dst_path,
1340 PATH_NODE_ORIGINS_DIR, TRUE, NULL,
1341 NULL, pool));
1343 /* Copy the txn-current file. */
1344 if (format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
1345 SVN_ERR(svn_io_dir_file_copy(src_path, dst_path, PATH_TXN_CURRENT, pool));
1347 /* Hotcopied FS is complete. Stamp it with a format file. */
1348 SVN_ERR(write_format(svn_path_join(dst_path, PATH_FORMAT, pool),
1349 format, max_files_per_dir, FALSE, pool));
1351 return SVN_NO_ERROR;
1354 svn_error_t *
1355 svn_fs_fs__youngest_rev(svn_revnum_t *youngest_p,
1356 svn_fs_t *fs,
1357 apr_pool_t *pool)
1359 fs_fs_data_t *ffd = fs->fsap_data;
1361 SVN_ERR(get_youngest(youngest_p, fs->path, pool));
1362 ffd->youngest_rev_cache = *youngest_p;
1364 return SVN_NO_ERROR;
1367 /* HEADER_CPATH lines need to be long enough to hold FSFS_MAX_PATH_LEN
1368 * bytes plus the stuff around them. */
1369 #define MAX_HEADERS_STR_LEN FSFS_MAX_PATH_LEN + sizeof(HEADER_CPATH ": \n") - 1
1371 /* Given a revision file FILE that has been pre-positioned at the
1372 beginning of a Node-Rev header block, read in that header block and
1373 store it in the apr_hash_t HEADERS. All allocations will be from
1374 POOL. */
1375 static svn_error_t * read_header_block(apr_hash_t **headers,
1376 apr_file_t *file,
1377 apr_pool_t *pool)
1379 *headers = apr_hash_make(pool);
1381 while (1)
1383 char header_str[MAX_HEADERS_STR_LEN];
1384 const char *name, *value;
1385 apr_size_t i = 0, header_len;
1386 apr_size_t limit;
1387 char *local_name, *local_value;
1389 limit = sizeof(header_str);
1390 SVN_ERR(svn_io_read_length_line(file, header_str, &limit, pool));
1392 if (strlen(header_str) == 0)
1393 break; /* end of header block */
1395 header_len = strlen(header_str);
1397 while (header_str[i] != ':')
1399 if (header_str[i] == '\0')
1400 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1401 _("Found malformed header in "
1402 "revision file"));
1403 i++;
1406 /* Create a 'name' string and point to it. */
1407 header_str[i] = '\0';
1408 name = header_str;
1410 /* Skip over the NULL byte and the space following it. */
1411 i += 2;
1413 if (i > header_len)
1414 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1415 _("Found malformed header in "
1416 "revision file"));
1418 value = header_str + i;
1420 local_name = apr_pstrdup(pool, name);
1421 local_value = apr_pstrdup(pool, value);
1423 apr_hash_set(*headers, local_name, APR_HASH_KEY_STRING, local_value);
1426 return SVN_NO_ERROR;
1429 /* Return SVN_ERR_FS_NO_SUCH_REVISION if the given revision is newer
1430 than the current youngest revision or is simply not a valid
1431 revision number, else return success.
1433 FSFS is based around the concept that commits only take effect when
1434 the number in "current" is bumped. Thus if there happens to be a rev
1435 or revprops file installed for a revision higher than the one recorded
1436 in "current" (because a commit failed between installing the rev file
1437 and bumping "current", or because an administrator rolled back the
1438 repository by resetting "current" without deleting rev files, etc), it
1439 ought to be completely ignored. This function provides the check
1440 by which callers can make that decision. */
1441 static svn_error_t *
1442 ensure_revision_exists(svn_fs_t *fs,
1443 svn_revnum_t rev,
1444 apr_pool_t *pool)
1446 fs_fs_data_t *ffd = fs->fsap_data;
1448 if (! SVN_IS_VALID_REVNUM(rev))
1449 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1450 _("Invalid revision number '%ld'"), rev);
1453 /* Did the revision exist the last time we checked the current
1454 file? */
1455 if (rev <= ffd->youngest_rev_cache)
1456 return SVN_NO_ERROR;
1458 SVN_ERR(get_youngest(&(ffd->youngest_rev_cache), fs->path, pool));
1460 /* Check again. */
1461 if (rev <= ffd->youngest_rev_cache)
1462 return SVN_NO_ERROR;
1464 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1465 _("No such revision %ld"), rev);
1468 /* Open the revision file for revision REV in filesystem FS and store
1469 the newly opened file in FILE. Seek to location OFFSET before
1470 returning. Perform temporary allocations in POOL. */
1471 static svn_error_t *
1472 open_and_seek_revision(apr_file_t **file,
1473 svn_fs_t *fs,
1474 svn_revnum_t rev,
1475 apr_off_t offset,
1476 apr_pool_t *pool)
1478 apr_file_t *rev_file;
1480 SVN_ERR(ensure_revision_exists(fs, rev, pool));
1482 SVN_ERR(svn_io_file_open(&rev_file, svn_fs_fs__path_rev(fs, rev, pool),
1483 APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
1485 SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
1487 *file = rev_file;
1489 return SVN_NO_ERROR;
1492 /* Open the representation for a node-revision in transaction TXN_ID
1493 in filesystem FS and store the newly opened file in FILE. Seek to
1494 location OFFSET before returning. Perform temporary allocations in
1495 POOL. Only appropriate for file contents, nor props or directory
1496 contents. */
1497 static svn_error_t *
1498 open_and_seek_transaction(apr_file_t **file,
1499 svn_fs_t *fs,
1500 const char *txn_id,
1501 representation_t *rep,
1502 apr_pool_t *pool)
1504 apr_file_t *rev_file;
1505 apr_off_t offset;
1507 SVN_ERR(svn_io_file_open(&rev_file, path_txn_proto_rev(fs, txn_id, pool),
1508 APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
1510 offset = rep->offset;
1511 SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
1513 *file = rev_file;
1515 return SVN_NO_ERROR;
1518 /* Given a node-id ID, and a representation REP in filesystem FS, open
1519 the correct file and seek to the correction location. Store this
1520 file in *FILE_P. Perform any allocations in POOL. */
1521 static svn_error_t *
1522 open_and_seek_representation(apr_file_t **file_p,
1523 svn_fs_t *fs,
1524 representation_t *rep,
1525 apr_pool_t *pool)
1527 if (! rep->txn_id)
1528 return open_and_seek_revision(file_p, fs, rep->revision, rep->offset,
1529 pool);
1530 else
1531 return open_and_seek_transaction(file_p, fs, rep->txn_id, rep, pool);
1534 /* Parse the description of a representation from STRING and store it
1535 into *REP_P. If the representation is mutable (the revision is
1536 given as -1), then use TXN_ID for the representation's txn_id
1537 field. If MUTABLE_REP_TRUNCATED is true, then this representation
1538 is for property or directory contents, and no information will be
1539 expected except the "-1" revision number for a mutable
1540 representation. Allocate *REP_P in POOL. */
1541 static svn_error_t *
1542 read_rep_offsets(representation_t **rep_p,
1543 char *string,
1544 const char *txn_id,
1545 svn_boolean_t mutable_rep_truncated,
1546 apr_pool_t *pool)
1548 representation_t *rep;
1549 char *str, *last_str;
1550 int i;
1552 rep = apr_pcalloc(pool, sizeof(*rep));
1553 *rep_p = rep;
1555 str = apr_strtok(string, " ", &last_str);
1556 if (str == NULL)
1557 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1558 _("Malformed text rep offset line in node-rev"));
1561 rep->revision = SVN_STR_TO_REV(str);
1562 if (rep->revision == SVN_INVALID_REVNUM)
1564 rep->txn_id = txn_id;
1565 if (mutable_rep_truncated)
1566 return SVN_NO_ERROR;
1569 str = apr_strtok(NULL, " ", &last_str);
1570 if (str == NULL)
1571 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1572 _("Malformed text rep offset line in node-rev"));
1574 rep->offset = apr_atoi64(str);
1576 str = apr_strtok(NULL, " ", &last_str);
1577 if (str == NULL)
1578 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1579 _("Malformed text rep offset line in node-rev"));
1581 rep->size = apr_atoi64(str);
1583 str = apr_strtok(NULL, " ", &last_str);
1584 if (str == NULL)
1585 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1586 _("Malformed text rep offset line in node-rev"));
1588 rep->expanded_size = apr_atoi64(str);
1590 /* Read in the MD5 hash. */
1591 str = apr_strtok(NULL, " ", &last_str);
1592 if ((str == NULL) || (strlen(str) != (APR_MD5_DIGESTSIZE * 2)))
1593 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1594 _("Malformed text rep offset line in node-rev"));
1596 /* Parse the hex MD5 hash into digest form. */
1597 for (i = 0; i < APR_MD5_DIGESTSIZE; i++)
1599 if ((! isxdigit(str[i * 2])) || (! isxdigit(str[i * 2 + 1])))
1600 return svn_error_create
1601 (SVN_ERR_FS_CORRUPT, NULL,
1602 _("Malformed text rep offset line in node-rev"));
1604 str[i * 2] = tolower(str[i * 2]);
1605 rep->checksum[i] = (str[i * 2] -
1606 ((str[i * 2] <= '9') ? '0' : ('a' - 10))) << 4;
1608 str[i * 2 + 1] = tolower(str[i * 2 + 1]);
1609 rep->checksum[i] |= (str[i * 2 + 1] -
1610 ((str[i * 2 + 1] <= '9') ? '0' : ('a' - 10)));
1613 return SVN_NO_ERROR;
1616 /* See svn_fs_fs__get_node_revision, which wraps this and adds another
1617 error. */
1618 static svn_error_t *
1619 get_node_revision_body(node_revision_t **noderev_p,
1620 svn_fs_t *fs,
1621 const svn_fs_id_t *id,
1622 apr_pool_t *pool)
1624 apr_file_t *revision_file;
1625 apr_hash_t *headers;
1626 node_revision_t *noderev;
1627 char *value;
1628 svn_error_t *err;
1630 if (svn_fs_fs__id_txn_id(id))
1632 /* This is a transaction node-rev. */
1633 err = svn_io_file_open(&revision_file, path_txn_node_rev(fs, id, pool),
1634 APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool);
1636 else
1638 /* This is a revision node-rev. */
1639 err = open_and_seek_revision(&revision_file, fs,
1640 svn_fs_fs__id_rev(id),
1641 svn_fs_fs__id_offset(id),
1642 pool);
1645 if (err)
1647 if (APR_STATUS_IS_ENOENT(err->apr_err))
1649 svn_error_clear(err);
1650 return svn_fs_fs__err_dangling_id(fs, id);
1653 return err;
1656 SVN_ERR(read_header_block(&headers, revision_file, pool) );
1658 noderev = apr_pcalloc(pool, sizeof(*noderev));
1660 /* Read the node-rev id. */
1661 value = apr_hash_get(headers, HEADER_ID, APR_HASH_KEY_STRING);
1662 if (value == NULL)
1663 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1664 _("Missing id field in node-rev"));
1666 SVN_ERR(svn_io_file_close(revision_file, pool));
1668 noderev->id = svn_fs_fs__id_parse(value, strlen(value), pool);
1670 /* Read the type. */
1671 value = apr_hash_get(headers, HEADER_TYPE, APR_HASH_KEY_STRING);
1673 if ((value == NULL) ||
1674 (strcmp(value, KIND_FILE) != 0 && strcmp(value, KIND_DIR)))
1675 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1676 _("Missing kind field in node-rev"));
1678 noderev->kind = (strcmp(value, KIND_FILE) == 0) ? svn_node_file
1679 : svn_node_dir;
1681 /* Read the 'count' field. */
1682 value = apr_hash_get(headers, HEADER_COUNT, APR_HASH_KEY_STRING);
1683 noderev->predecessor_count = (value == NULL) ? 0 : atoi(value);
1685 /* Get the properties location. */
1686 value = apr_hash_get(headers, HEADER_PROPS, APR_HASH_KEY_STRING);
1687 if (value)
1689 SVN_ERR(read_rep_offsets(&noderev->prop_rep, value,
1690 svn_fs_fs__id_txn_id(id), TRUE, pool));
1693 /* Get the data location. */
1694 value = apr_hash_get(headers, HEADER_TEXT, APR_HASH_KEY_STRING);
1695 if (value)
1697 SVN_ERR(read_rep_offsets(&noderev->data_rep, value,
1698 svn_fs_fs__id_txn_id(id),
1699 (noderev->kind == svn_node_dir), pool));
1702 /* Get the created path. */
1703 value = apr_hash_get(headers, HEADER_CPATH, APR_HASH_KEY_STRING);
1704 if (value == NULL)
1706 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1707 _("Missing cpath in node-rev"));
1709 else
1711 noderev->created_path = apr_pstrdup(pool, value);
1714 /* Get the predecessor ID. */
1715 value = apr_hash_get(headers, HEADER_PRED, APR_HASH_KEY_STRING);
1716 if (value)
1717 noderev->predecessor_id = svn_fs_fs__id_parse(value, strlen(value),
1718 pool);
1720 /* Get the copyroot. */
1721 value = apr_hash_get(headers, HEADER_COPYROOT, APR_HASH_KEY_STRING);
1722 if (value == NULL)
1724 noderev->copyroot_path = apr_pstrdup(pool, noderev->created_path);
1725 noderev->copyroot_rev = svn_fs_fs__id_rev(noderev->id);
1727 else
1729 char *str, *last_str;
1731 str = apr_strtok(value, " ", &last_str);
1732 if (str == NULL)
1733 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1734 _("Malformed copyroot line in node-rev"));
1736 noderev->copyroot_rev = atoi(str);
1738 if (last_str == NULL)
1739 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1740 _("Malformed copyroot line in node-rev"));
1741 noderev->copyroot_path = apr_pstrdup(pool, last_str);
1744 /* Get the copyfrom. */
1745 value = apr_hash_get(headers, HEADER_COPYFROM, APR_HASH_KEY_STRING);
1746 if (value == NULL)
1748 noderev->copyfrom_path = NULL;
1749 noderev->copyfrom_rev = SVN_INVALID_REVNUM;
1751 else
1753 char *str, *last_str;
1755 str = apr_strtok(value, " ", &last_str);
1756 if (str == NULL)
1757 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1758 _("Malformed copyfrom line in node-rev"));
1760 noderev->copyfrom_rev = atoi(str);
1762 if (last_str == NULL)
1763 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1764 _("Malformed copyfrom line in node-rev"));
1765 noderev->copyfrom_path = apr_pstrdup(pool, last_str);
1768 /* Get whether this is a fresh txn root. */
1769 value = apr_hash_get(headers, HEADER_FRESHTXNRT, APR_HASH_KEY_STRING);
1770 noderev->is_fresh_txn_root = (value != NULL);
1772 /* Get the mergeinfo count. */
1773 value = apr_hash_get(headers, HEADER_MINFO_CNT, APR_HASH_KEY_STRING);
1774 noderev->mergeinfo_count = (value == NULL) ? 0 : apr_atoi64(value);
1776 /* Get whether *this* node has mergeinfo. */
1777 value = apr_hash_get(headers, HEADER_MINFO_HERE, APR_HASH_KEY_STRING);
1778 noderev->has_mergeinfo = (value != NULL);
1780 *noderev_p = noderev;
1782 return SVN_NO_ERROR;
1785 svn_error_t *
1786 svn_fs_fs__get_node_revision(node_revision_t **noderev_p,
1787 svn_fs_t *fs,
1788 const svn_fs_id_t *id,
1789 apr_pool_t *pool)
1791 svn_error_t *err = get_node_revision_body(noderev_p, fs, id, pool);
1792 if (err && err->apr_err == SVN_ERR_FS_CORRUPT)
1794 svn_string_t *id_string = svn_fs_fs__id_unparse(id, pool);
1795 return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
1796 "Corrupt node-revision '%s'",
1797 id_string->data);
1799 return err;
1803 /* Return a formatted string that represents the location of
1804 representation REP. If MUTABLE_REP_TRUNCATED is given, the rep is
1805 for props or dir contents, and only a "-1" revision number will be
1806 given for a mutable rep. Perform the allocation from POOL. */
1807 static const char *
1808 representation_string(representation_t *rep,
1809 svn_boolean_t mutable_rep_truncated, apr_pool_t *pool)
1811 if (rep->txn_id && mutable_rep_truncated)
1812 return "-1";
1813 else
1814 return apr_psprintf(pool, "%ld %" APR_OFF_T_FMT " %" SVN_FILESIZE_T_FMT
1815 " %" SVN_FILESIZE_T_FMT " %s",
1816 rep->revision, rep->offset, rep->size,
1817 rep->expanded_size,
1818 svn_md5_digest_to_cstring_display(rep->checksum,
1819 pool));
1822 /* Write the node-revision NODEREV into the file FILE. Only write
1823 mergeinfo-related metadata if INCLUDE_MERGEINFO is true. Temporary
1824 allocations are from POOL. */
1825 static svn_error_t *
1826 write_noderev_txn(apr_file_t *file,
1827 node_revision_t *noderev,
1828 svn_boolean_t include_mergeinfo,
1829 apr_pool_t *pool)
1831 svn_stream_t *outfile;
1833 outfile = svn_stream_from_aprfile(file, pool);
1835 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_ID ": %s\n",
1836 svn_fs_fs__id_unparse(noderev->id,
1837 pool)->data));
1839 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_TYPE ": %s\n",
1840 (noderev->kind == svn_node_file) ?
1841 KIND_FILE : KIND_DIR));
1843 if (noderev->predecessor_id)
1844 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_PRED ": %s\n",
1845 svn_fs_fs__id_unparse(noderev->predecessor_id,
1846 pool)->data));
1848 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COUNT ": %d\n",
1849 noderev->predecessor_count));
1851 if (noderev->data_rep)
1852 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_TEXT ": %s\n",
1853 representation_string(noderev->data_rep,
1854 (noderev->kind
1855 == svn_node_dir),
1856 pool)));
1858 if (noderev->prop_rep)
1859 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_PROPS ": %s\n",
1860 representation_string(noderev->prop_rep, TRUE,
1861 pool)));
1863 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_CPATH ": %s\n",
1864 noderev->created_path));
1866 if (noderev->copyfrom_path)
1867 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COPYFROM ": %ld"
1868 " %s\n",
1869 noderev->copyfrom_rev,
1870 noderev->copyfrom_path));
1872 if ((noderev->copyroot_rev != svn_fs_fs__id_rev(noderev->id)) ||
1873 (strcmp(noderev->copyroot_path, noderev->created_path) != 0))
1874 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COPYROOT ": %ld"
1875 " %s\n",
1876 noderev->copyroot_rev,
1877 noderev->copyroot_path));
1879 if (noderev->is_fresh_txn_root)
1880 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_FRESHTXNRT ": y\n"));
1882 if (include_mergeinfo)
1884 if (noderev->mergeinfo_count > 0)
1885 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_MINFO_CNT ": %"
1886 APR_INT64_T_FMT "\n",
1887 noderev->mergeinfo_count));
1889 if (noderev->has_mergeinfo)
1890 SVN_ERR(svn_stream_printf(outfile, pool, HEADER_MINFO_HERE ": y\n"));
1893 SVN_ERR(svn_stream_printf(outfile, pool, "\n"));
1895 return SVN_NO_ERROR;
1898 svn_error_t *
1899 svn_fs_fs__put_node_revision(svn_fs_t *fs,
1900 const svn_fs_id_t *id,
1901 node_revision_t *noderev,
1902 svn_boolean_t fresh_txn_root,
1903 apr_pool_t *pool)
1905 apr_file_t *noderev_file;
1906 const char *txn_id = svn_fs_fs__id_txn_id(id);
1908 noderev->is_fresh_txn_root = fresh_txn_root;
1910 if (! txn_id)
1911 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1912 _("Attempted to write to non-transaction"));
1914 SVN_ERR(svn_io_file_open(&noderev_file, path_txn_node_rev(fs, id, pool),
1915 APR_WRITE | APR_CREATE | APR_TRUNCATE
1916 | APR_BUFFERED, APR_OS_DEFAULT, pool));
1918 SVN_ERR(write_noderev_txn(noderev_file, noderev,
1919 svn_fs_fs__fs_supports_mergeinfo(fs),
1920 pool));
1922 SVN_ERR(svn_io_file_close(noderev_file, pool));
1924 return SVN_NO_ERROR;
1928 /* This structure is used to hold the information associated with a
1929 REP line. */
1930 struct rep_args
1932 svn_boolean_t is_delta;
1933 svn_boolean_t is_delta_vs_empty;
1935 svn_revnum_t base_revision;
1936 apr_off_t base_offset;
1937 apr_size_t base_length;
1940 /* Read the next line from file FILE and parse it as a text
1941 representation entry. Return the parsed entry in *REP_ARGS_P.
1942 Perform all allocations in POOL. */
1943 static svn_error_t *
1944 read_rep_line(struct rep_args **rep_args_p,
1945 apr_file_t *file,
1946 apr_pool_t *pool)
1948 char buffer[160];
1949 apr_size_t limit;
1950 struct rep_args *rep_args;
1951 char *str, *last_str;
1953 limit = sizeof(buffer);
1954 SVN_ERR(svn_io_read_length_line(file, buffer, &limit, pool));
1956 rep_args = apr_pcalloc(pool, sizeof(*rep_args));
1957 rep_args->is_delta = FALSE;
1959 if (strcmp(buffer, REP_PLAIN) == 0)
1961 *rep_args_p = rep_args;
1962 return SVN_NO_ERROR;
1965 if (strcmp(buffer, REP_DELTA) == 0)
1967 /* This is a delta against the empty stream. */
1968 rep_args->is_delta = TRUE;
1969 rep_args->is_delta_vs_empty = TRUE;
1970 *rep_args_p = rep_args;
1971 return SVN_NO_ERROR;
1974 rep_args->is_delta = TRUE;
1975 rep_args->is_delta_vs_empty = FALSE;
1977 /* We have hopefully a DELTA vs. a non-empty base revision. */
1978 str = apr_strtok(buffer, " ", &last_str);
1979 if (! str || (strcmp(str, REP_DELTA) != 0)) goto err;
1981 str = apr_strtok(NULL, " ", &last_str);
1982 if (! str) goto err;
1983 rep_args->base_revision = atol(str);
1985 str = apr_strtok(NULL, " ", &last_str);
1986 if (! str) goto err;
1987 rep_args->base_offset = (apr_off_t) apr_atoi64(str);
1989 str = apr_strtok(NULL, " ", &last_str);
1990 if (! str) goto err;
1991 rep_args->base_length = (apr_size_t) apr_atoi64(str);
1993 *rep_args_p = rep_args;
1994 return SVN_NO_ERROR;
1996 err:
1997 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1998 _("Malformed representation header"));
2001 /* Given a revision file REV_FILE, find the Node-ID of the header
2002 located at OFFSET and store it in *ID_P. Allocate temporary
2003 variables from POOL. */
2004 static svn_error_t *
2005 get_fs_id_at_offset(svn_fs_id_t **id_p,
2006 apr_file_t *rev_file,
2007 apr_off_t offset,
2008 apr_pool_t *pool)
2010 svn_fs_id_t *id;
2011 apr_hash_t *headers;
2012 const char *node_id_str;
2014 SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
2016 SVN_ERR(read_header_block(&headers, rev_file, pool));
2018 node_id_str = apr_hash_get(headers, HEADER_ID, APR_HASH_KEY_STRING);
2020 if (node_id_str == NULL)
2021 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2022 _("Missing node-id in node-rev"));
2024 id = svn_fs_fs__id_parse(node_id_str, strlen(node_id_str), pool);
2026 if (id == NULL)
2027 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2028 _("Corrupt node-id in node-rev"));
2030 *id_p = id;
2032 return SVN_NO_ERROR;
2036 /* Given an open revision file REV_FILE, locate the trailer that
2037 specifies the offset to the root node-id and to the changed path
2038 information. Store the root node offset in *ROOT_OFFSET and the
2039 changed path offset in *CHANGES_OFFSET. If either of these
2040 pointers is NULL, do nothing with it. Allocate temporary variables
2041 from POOL. */
2042 static svn_error_t *
2043 get_root_changes_offset(apr_off_t *root_offset,
2044 apr_off_t *changes_offset,
2045 apr_file_t *rev_file,
2046 apr_pool_t *pool)
2048 apr_off_t offset;
2049 char buf[64];
2050 int i, num_bytes;
2051 apr_size_t len;
2053 /* We will assume that the last line containing the two offsets
2054 will never be longer than 64 characters. */
2055 offset = 0;
2056 SVN_ERR(svn_io_file_seek(rev_file, APR_END, &offset, pool));
2058 offset -= sizeof(buf);
2059 SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
2061 /* Read in this last block, from which we will identify the last line. */
2062 len = sizeof(buf);
2063 SVN_ERR(svn_io_file_read(rev_file, buf, &len, pool));
2065 /* This cast should be safe since the maximum amount read, 64, will
2066 never be bigger than the size of an int. */
2067 num_bytes = (int) len;
2069 /* The last byte should be a newline. */
2070 if (buf[num_bytes - 1] != '\n')
2072 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2073 _("Revision file lacks trailing newline"));
2076 /* Look for the next previous newline. */
2077 for (i = num_bytes - 2; i >= 0; i--)
2079 if (buf[i] == '\n') break;
2082 if (i < 0)
2084 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2085 _("Final line in revision file longer than 64 "
2086 "characters"));
2089 i++;
2091 if (root_offset)
2092 *root_offset = apr_atoi64(&buf[i]);
2094 /* find the next space */
2095 for ( ; i < (num_bytes - 2) ; i++)
2096 if (buf[i] == ' ') break;
2098 if (i == (num_bytes - 2))
2099 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2100 _("Final line in revision file missing space"));
2102 i++;
2104 /* note that apr_atoi64() will stop reading as soon as it encounters
2105 the final newline. */
2106 if (changes_offset)
2107 *changes_offset = apr_atoi64(&buf[i]);
2109 return SVN_NO_ERROR;
2112 svn_error_t *
2113 svn_fs_fs__rev_get_root(svn_fs_id_t **root_id_p,
2114 svn_fs_t *fs,
2115 svn_revnum_t rev,
2116 apr_pool_t *pool)
2118 fs_fs_data_t *ffd = fs->fsap_data;
2119 apr_file_t *revision_file;
2120 apr_off_t root_offset;
2121 svn_fs_id_t *root_id;
2122 svn_error_t *err;
2123 const char *rev_str = apr_psprintf(pool, "%ld", rev);
2124 svn_fs_id_t *cached_id;
2126 SVN_ERR(ensure_revision_exists(fs, rev, pool));
2128 /* Calculate an index into the revroot id cache */
2129 cached_id = apr_hash_get(ffd->rev_root_id_cache,
2130 rev_str,
2131 APR_HASH_KEY_STRING);
2133 if (cached_id)
2135 *root_id_p = svn_fs_fs__id_copy(cached_id, pool);
2136 return SVN_NO_ERROR;
2139 err = svn_io_file_open(&revision_file, svn_fs_fs__path_rev(fs, rev, pool),
2140 APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool);
2141 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
2143 svn_error_clear(err);
2144 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
2145 _("No such revision %ld"), rev);
2147 else if (err)
2148 return err;
2151 SVN_ERR(get_root_changes_offset(&root_offset, NULL, revision_file, pool));
2153 SVN_ERR(get_fs_id_at_offset(&root_id, revision_file, root_offset, pool));
2155 SVN_ERR(svn_io_file_close(revision_file, pool));
2157 /* Make sure our cache size doesn't grow without bounds. */
2158 if (apr_hash_count(ffd->rev_root_id_cache) >= NUM_RRI_CACHE_ENTRIES)
2160 /* In order to only use one pool for the whole cache, we need to
2161 * completely wipe it to expire entries! */
2162 svn_pool_clear(ffd->rev_root_id_cache_pool);
2163 ffd->rev_root_id_cache = apr_hash_make(ffd->rev_root_id_cache_pool);
2166 /* Cache the answer, copying both the key and value into the cache's
2167 pool. */
2168 apr_hash_set(ffd->rev_root_id_cache,
2169 apr_pstrdup(ffd->rev_root_id_cache_pool, rev_str),
2170 APR_HASH_KEY_STRING,
2171 svn_fs_fs__id_copy(root_id, ffd->rev_root_id_cache_pool));
2173 *root_id_p = root_id;
2175 return SVN_NO_ERROR;
2178 svn_error_t *
2179 svn_fs_fs__set_revision_proplist(svn_fs_t *fs,
2180 svn_revnum_t rev,
2181 apr_hash_t *proplist,
2182 apr_pool_t *pool)
2184 const char *final_path = path_revprops(fs, rev, pool);
2185 const char *tmp_path;
2186 apr_file_t *f;
2188 SVN_ERR(ensure_revision_exists(fs, rev, pool));
2190 SVN_ERR(svn_io_open_unique_file2
2191 (&f, &tmp_path, final_path, ".tmp", svn_io_file_del_none, pool));
2192 SVN_ERR(svn_hash_write(proplist, f, pool));
2193 SVN_ERR(svn_io_file_close(f, pool));
2194 /* We use the rev file of this revision as the perms reference,
2195 because when setting revprops for the first time, the revprop
2196 file won't exist and therefore can't serve as its own reference.
2197 (Whereas the rev file should already exist at this point.) */
2198 SVN_ERR(svn_fs_fs__move_into_place(tmp_path, final_path,
2199 svn_fs_fs__path_rev(fs, rev, pool),
2200 pool));
2202 return SVN_NO_ERROR;
2205 svn_error_t *
2206 svn_fs_fs__revision_proplist(apr_hash_t **proplist_p,
2207 svn_fs_t *fs,
2208 svn_revnum_t rev,
2209 apr_pool_t *pool)
2211 apr_file_t *revprop_file = NULL;
2212 apr_hash_t *proplist;
2213 svn_error_t *err = SVN_NO_ERROR;
2214 int i;
2215 apr_pool_t *iterpool;
2217 SVN_ERR(ensure_revision_exists(fs, rev, pool));
2219 proplist = apr_hash_make(pool);
2220 iterpool = svn_pool_create(pool);
2221 for (i = 0; i < RECOVERABLE_RETRY_COUNT; i++)
2223 svn_pool_clear(iterpool);
2225 /* Clear err here rather than after finding a recoverable error so
2226 * we can return that error on the last iteration of the loop. */
2227 svn_error_clear(err);
2228 err = svn_io_file_open(&revprop_file, path_revprops(fs, rev, iterpool),
2229 APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
2230 iterpool);
2231 if (err)
2233 if (APR_STATUS_IS_ENOENT(err->apr_err))
2235 svn_error_clear(err);
2236 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
2237 _("No such revision %ld"), rev);
2239 #ifdef ESTALE
2240 else if (APR_TO_OS_ERROR(err->apr_err) == ESTALE
2241 || APR_TO_OS_ERROR(err->apr_err) == EIO
2242 || APR_TO_OS_ERROR(err->apr_err) == ENOENT)
2243 continue;
2244 #endif
2245 return err;
2248 SVN_ERR(svn_hash__clear(proplist));
2249 RETRY_RECOVERABLE(err, revprop_file,
2250 svn_hash_read2(proplist,
2251 svn_stream_from_aprfile(revprop_file,
2252 iterpool),
2253 SVN_HASH_TERMINATOR, pool));
2255 IGNORE_RECOVERABLE(err, svn_io_file_close(revprop_file, iterpool));
2257 break;
2259 if (err)
2260 return err;
2261 svn_pool_destroy(iterpool);
2263 *proplist_p = proplist;
2265 return SVN_NO_ERROR;
2268 /* Represents where in the current svndiff data block each
2269 representation is. */
2270 struct rep_state
2272 apr_file_t *file;
2273 apr_off_t start; /* The starting offset for the raw
2274 svndiff/plaintext data minus header. */
2275 apr_off_t off; /* The current offset into the file. */
2276 apr_off_t end; /* The end offset of the raw data. */
2277 int ver; /* If a delta, what svndiff version? */
2278 int chunk_index;
2281 /* See create_rep_state, which wraps this and adds another error. */
2282 static svn_error_t *
2283 create_rep_state_body(struct rep_state **rep_state,
2284 struct rep_args **rep_args,
2285 representation_t *rep,
2286 svn_fs_t *fs,
2287 apr_pool_t *pool)
2289 struct rep_state *rs = apr_pcalloc(pool, sizeof(*rs));
2290 struct rep_args *ra;
2291 unsigned char buf[4];
2293 SVN_ERR(open_and_seek_representation(&rs->file, fs, rep, pool));
2294 SVN_ERR(read_rep_line(&ra, rs->file, pool));
2295 SVN_ERR(get_file_offset(&rs->start, rs->file, pool));
2296 rs->off = rs->start;
2297 rs->end = rs->start + rep->size;
2298 *rep_state = rs;
2299 *rep_args = ra;
2301 if (ra->is_delta == FALSE)
2302 /* This is a plaintext, so just return the current rep_state. */
2303 return SVN_NO_ERROR;
2305 /* We are dealing with a delta, find out what version. */
2306 SVN_ERR(svn_io_file_read_full(rs->file, buf, sizeof(buf), NULL, pool));
2307 if (! ((buf[0] == 'S') && (buf[1] == 'V') && (buf[2] == 'N')))
2308 return svn_error_create
2309 (SVN_ERR_FS_CORRUPT, NULL,
2310 _("Malformed svndiff data in representation"));
2311 rs->ver = buf[3];
2312 rs->chunk_index = 0;
2313 rs->off += 4;
2315 return SVN_NO_ERROR;
2318 /* Read the rep args for REP in filesystem FS and create a rep_state
2319 for reading the representation. Return the rep_state in *REP_STATE
2320 and the rep args in *REP_ARGS, both allocated in POOL. */
2321 static svn_error_t *
2322 create_rep_state(struct rep_state **rep_state,
2323 struct rep_args **rep_args,
2324 representation_t *rep,
2325 svn_fs_t *fs,
2326 apr_pool_t *pool)
2328 svn_error_t *err = create_rep_state_body(rep_state, rep_args, rep, fs, pool);
2329 if (err && err->apr_err == SVN_ERR_FS_CORRUPT)
2331 /* ### This always returns "-1" for transaction reps, because
2332 ### this particular bit of code doesn't know if the rep is
2333 ### stored in the protorev or in the mutable area (for props
2334 ### or dir contents). It is pretty rare for FSFS to *read*
2335 ### from the protorev file, though, so this is probably OK.
2336 ### And anyone going to debug corruption errors is probably
2337 ### going to jump straight to this comment anyway! */
2338 return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
2339 "Corrupt representation '%s'",
2340 representation_string(rep, TRUE, pool));
2342 return err;
2345 /* Build an array of rep_state structures in *LIST giving the delta
2346 reps from first_rep to a plain-text or self-compressed rep. Set
2347 *SRC_STATE to the plain-text rep we find at the end of the chain,
2348 or to NULL if the final delta representation is self-compressed.
2349 The representation to start from is designated by filesystem FS, id
2350 ID, and representation REP. */
2351 static svn_error_t *
2352 build_rep_list(apr_array_header_t **list,
2353 struct rep_state **src_state,
2354 svn_fs_t *fs,
2355 representation_t *first_rep,
2356 apr_pool_t *pool)
2358 representation_t rep;
2359 struct rep_state *rs;
2360 struct rep_args *rep_args;
2362 *list = apr_array_make(pool, 1, sizeof(struct rep_state *));
2363 rep = *first_rep;
2365 while (1)
2367 SVN_ERR(create_rep_state(&rs, &rep_args, &rep, fs, pool));
2368 if (rep_args->is_delta == FALSE)
2370 /* This is a plaintext, so just return the current rep_state. */
2371 *src_state = rs;
2372 return SVN_NO_ERROR;
2375 /* Push this rep onto the list. If it's self-compressed, we're done. */
2376 APR_ARRAY_PUSH(*list, struct rep_state *) = rs;
2377 if (rep_args->is_delta_vs_empty)
2379 *src_state = NULL;
2380 return SVN_NO_ERROR;
2383 rep.revision = rep_args->base_revision;
2384 rep.offset = rep_args->base_offset;
2385 rep.size = rep_args->base_length;
2386 rep.txn_id = NULL;
2391 struct rep_read_baton
2393 /* The FS from which we're reading. */
2394 svn_fs_t *fs;
2396 /* The state of all prior delta representations. */
2397 apr_array_header_t *rs_list;
2399 /* The plaintext state, if there is a plaintext. */
2400 struct rep_state *src_state;
2402 /* The index of the current delta chunk, if we are reading a delta. */
2403 int chunk_index;
2405 /* The buffer where we store undeltified data. */
2406 char *buf;
2407 apr_size_t buf_pos;
2408 apr_size_t buf_len;
2410 /* An MD5 context for summing the data read in order to verify it. */
2411 struct apr_md5_ctx_t md5_context;
2412 svn_boolean_t checksum_finalized;
2414 /* The stored checksum of the representation we are reading, its
2415 length, and the amount we've read so far. Some of this
2416 information is redundant with rs_list and src_state, but it's
2417 convenient for the checksumming code to have it here. */
2418 unsigned char checksum[APR_MD5_DIGESTSIZE];
2419 svn_filesize_t len;
2420 svn_filesize_t off;
2422 /* Used for temporary allocations during the read. */
2423 apr_pool_t *pool;
2425 /* Pool used to store file handles and other data that is persistant
2426 for the entire stream read. */
2427 apr_pool_t *filehandle_pool;
2430 /* Create a rep_read_baton structure for node revision NODEREV in
2431 filesystem FS and store it in *RB_P. Perform all allocations in
2432 POOL. If rep is mutable, it must be for file contents. */
2433 static svn_error_t *
2434 rep_read_get_baton(struct rep_read_baton **rb_p,
2435 svn_fs_t *fs,
2436 representation_t *rep,
2437 apr_pool_t *pool)
2439 struct rep_read_baton *b;
2441 b = apr_pcalloc(pool, sizeof(*b));
2442 b->fs = fs;
2443 b->chunk_index = 0;
2444 b->buf = NULL;
2445 apr_md5_init(&(b->md5_context));
2446 b->checksum_finalized = FALSE;
2447 memcpy(b->checksum, rep->checksum, sizeof(b->checksum));
2448 b->len = rep->expanded_size;
2449 b->off = 0;
2450 b->pool = svn_pool_create(pool);
2451 b->filehandle_pool = svn_pool_create(pool);
2453 SVN_ERR(build_rep_list(&b->rs_list, &b->src_state, fs, rep,
2454 b->filehandle_pool));
2456 /* Save our output baton. */
2457 *rb_p = b;
2459 return SVN_NO_ERROR;
2462 /* Skip forwards to THIS_CHUNK in REP_STATE and then read the next delta
2463 window into *NWIN. */
2464 static svn_error_t *
2465 read_window(svn_txdelta_window_t **nwin, int this_chunk, struct rep_state *rs,
2466 apr_pool_t *pool)
2468 svn_stream_t *stream;
2470 assert(rs->chunk_index <= this_chunk);
2472 /* Skip windows to reach the current chunk if we aren't there yet. */
2473 while (rs->chunk_index < this_chunk)
2475 SVN_ERR(svn_txdelta_skip_svndiff_window(rs->file, rs->ver, pool));
2476 rs->chunk_index++;
2477 SVN_ERR(get_file_offset(&rs->off, rs->file, pool));
2478 if (rs->off >= rs->end)
2479 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2480 _("Reading one svndiff window read "
2481 "beyond the end of the "
2482 "representation"));
2485 /* Read the next window. */
2486 stream = svn_stream_from_aprfile(rs->file, pool);
2487 SVN_ERR(svn_txdelta_read_svndiff_window(nwin, stream, rs->ver, pool));
2488 rs->chunk_index++;
2489 SVN_ERR(get_file_offset(&rs->off, rs->file, pool));
2491 if (rs->off > rs->end)
2492 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2493 _("Reading one svndiff window read beyond "
2494 "the end of the representation"));
2496 return SVN_NO_ERROR;
2499 /* Get one delta window that is a result of combining all but the last deltas
2500 from the current desired representation identified in *RB, to its
2501 final base representation. Store the window in *RESULT. */
2502 static svn_error_t *
2503 get_combined_window(svn_txdelta_window_t **result,
2504 struct rep_read_baton *rb)
2506 apr_pool_t *pool, *new_pool;
2507 int i;
2508 svn_txdelta_window_t *window, *nwin;
2509 struct rep_state *rs;
2511 assert(rb->rs_list->nelts >= 2);
2513 pool = svn_pool_create(rb->pool);
2515 /* Read the next window from the original rep. */
2516 rs = APR_ARRAY_IDX(rb->rs_list, 0, struct rep_state *);
2517 SVN_ERR(read_window(&window, rb->chunk_index, rs, pool));
2519 /* Combine in the windows from the other delta reps, if needed. */
2520 for (i = 1; i < rb->rs_list->nelts - 1; i++)
2522 if (window->src_ops == 0)
2523 break;
2525 rs = APR_ARRAY_IDX(rb->rs_list, i, struct rep_state *);
2527 SVN_ERR(read_window(&nwin, rb->chunk_index, rs, pool));
2529 /* Combine this window with the current one. Cycles pools so that we
2530 only need to hold three windows at a time. */
2531 new_pool = svn_pool_create(rb->pool);
2532 window = svn_txdelta_compose_windows(nwin, window, new_pool);
2533 svn_pool_destroy(pool);
2534 pool = new_pool;
2537 *result = window;
2538 return SVN_NO_ERROR;
2541 static svn_error_t *
2542 rep_read_contents_close(void *baton)
2544 struct rep_read_baton *rb = baton;
2546 svn_pool_destroy(rb->pool);
2547 svn_pool_destroy(rb->filehandle_pool);
2549 return SVN_NO_ERROR;
2552 /* Return the next *LEN bytes of the rep and store them in *BUF. */
2553 static svn_error_t *
2554 get_contents(struct rep_read_baton *rb,
2555 char *buf,
2556 apr_size_t *len)
2558 apr_size_t copy_len, remaining = *len, tlen;
2559 char *sbuf, *tbuf, *cur = buf;
2560 struct rep_state *rs;
2561 svn_txdelta_window_t *cwindow, *lwindow;
2563 /* Special case for when there are no delta reps, only a plain
2564 text. */
2565 if (rb->rs_list->nelts == 0)
2567 copy_len = remaining;
2568 rs = rb->src_state;
2569 if (((apr_off_t) copy_len) > rs->end - rs->off)
2570 copy_len = (apr_size_t) (rs->end - rs->off);
2571 SVN_ERR(svn_io_file_read_full(rs->file, cur, copy_len, NULL,
2572 rb->pool));
2573 rs->off += copy_len;
2574 *len = copy_len;
2575 return SVN_NO_ERROR;
2578 while (remaining > 0)
2580 /* If we have buffered data from a previous chunk, use that. */
2581 if (rb->buf)
2583 /* Determine how much to copy from the buffer. */
2584 copy_len = rb->buf_len - rb->buf_pos;
2585 if (copy_len > remaining)
2586 copy_len = remaining;
2588 /* Actually copy the data. */
2589 memcpy(cur, rb->buf + rb->buf_pos, copy_len);
2590 rb->buf_pos += copy_len;
2591 cur += copy_len;
2592 remaining -= copy_len;
2594 /* If the buffer is all used up, clear it and empty the
2595 local pool. */
2596 if (rb->buf_pos == rb->buf_len)
2598 svn_pool_clear(rb->pool);
2599 rb->buf = NULL;
2602 else
2605 rs = APR_ARRAY_IDX(rb->rs_list, 0, struct rep_state *);
2606 if (rs->off == rs->end)
2607 break;
2609 /* Get more buffered data by evaluating a chunk. */
2610 if (rb->rs_list->nelts > 1)
2611 SVN_ERR(get_combined_window(&cwindow, rb));
2612 else
2613 cwindow = NULL;
2614 if (!cwindow || cwindow->src_ops > 0)
2616 rs = APR_ARRAY_IDX(rb->rs_list, rb->rs_list->nelts - 1,
2617 struct rep_state *);
2618 /* Read window from last representation in list. */
2619 /* We apply this window directly instead of combining it
2620 with the others. We do this because vdelta used to
2621 be used for deltas against the empty stream, which
2622 will trigger quadratic behaviour in the delta
2623 combiner. It's still likely that we'll find such
2624 deltas in an old repository; it may be worth
2625 considering whether or not this special case is still
2626 needed in the future, though. */
2627 SVN_ERR(read_window(&lwindow, rb->chunk_index, rs, rb->pool));
2629 if (lwindow->src_ops > 0)
2631 if (! rb->src_state)
2632 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2633 _("svndiff data requested "
2634 "non-existent source"));
2635 rs = rb->src_state;
2636 sbuf = apr_palloc(rb->pool, lwindow->sview_len);
2637 if (! ((rs->start + lwindow->sview_offset) < rs->end))
2638 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2639 _("svndiff requested position "
2640 "beyond end of stream"));
2641 if ((rs->start + lwindow->sview_offset) != rs->off)
2643 rs->off = rs->start + lwindow->sview_offset;
2644 SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off,
2645 rb->pool));
2647 SVN_ERR(svn_io_file_read_full(rs->file, sbuf,
2648 lwindow->sview_len,
2649 NULL, rb->pool));
2650 rs->off += lwindow->sview_len;
2652 else
2653 sbuf = NULL;
2655 /* Apply lwindow to source. */
2656 tlen = lwindow->tview_len;
2657 tbuf = apr_palloc(rb->pool, tlen);
2658 svn_txdelta_apply_instructions(lwindow, sbuf, tbuf,
2659 &tlen);
2660 if (tlen != lwindow->tview_len)
2661 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2662 _("svndiff window length is "
2663 "corrupt"));
2664 sbuf = tbuf;
2666 else
2667 sbuf = NULL;
2669 rb->chunk_index++;
2671 if (cwindow)
2673 rb->buf_len = cwindow->tview_len;
2674 rb->buf = apr_palloc(rb->pool, rb->buf_len);
2675 svn_txdelta_apply_instructions(cwindow, sbuf, rb->buf,
2676 &rb->buf_len);
2677 if (rb->buf_len != cwindow->tview_len)
2678 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2679 _("svndiff window length is "
2680 "corrupt"));
2682 else
2684 rb->buf_len = lwindow->tview_len;
2685 rb->buf = sbuf;
2688 rb->buf_pos = 0;
2692 *len = cur - buf;
2694 return SVN_NO_ERROR;
2697 /* BATON is of type `rep_read_baton'; read the next *LEN bytes of the
2698 representation and store them in *BUF. Sum as we read and verify
2699 the MD5 sum at the end. */
2700 static svn_error_t *
2701 rep_read_contents(void *baton,
2702 char *buf,
2703 apr_size_t *len)
2705 struct rep_read_baton *rb = baton;
2707 /* Get the next block of data. */
2708 SVN_ERR(get_contents(rb, buf, len));
2710 /* Perform checksumming. We want to check the checksum as soon as
2711 the last byte of data is read, in case the caller never performs
2712 a short read, but we don't want to finalize the MD5 context
2713 twice. */
2714 if (!rb->checksum_finalized)
2716 apr_md5_update(&rb->md5_context, buf, *len);
2717 rb->off += *len;
2718 if (rb->off == rb->len)
2720 unsigned char checksum[APR_MD5_DIGESTSIZE];
2722 rb->checksum_finalized = TRUE;
2723 apr_md5_final(checksum, &rb->md5_context);
2724 if (! svn_md5_digests_match(checksum, rb->checksum))
2725 return svn_error_createf
2726 (SVN_ERR_FS_CORRUPT, NULL,
2727 _("Checksum mismatch while reading representation:\n"
2728 " expected: %s\n"
2729 " actual: %s\n"),
2730 svn_md5_digest_to_cstring_display(rb->checksum, rb->pool),
2731 svn_md5_digest_to_cstring_display(checksum, rb->pool));
2734 return SVN_NO_ERROR;
2737 /* Return a stream in *CONTENTS_P that will read the contents of a
2738 representation stored at the location given by REP. Appropriate
2739 for any kind of immutable representation, but only for file
2740 contents (not props or directory contents) in mutable
2741 representations.
2743 If REP is NULL, the representation is assumed to be empty, and the
2744 empty stream is returned.
2746 static svn_error_t *
2747 read_representation(svn_stream_t **contents_p,
2748 svn_fs_t *fs,
2749 representation_t *rep,
2750 apr_pool_t *pool)
2752 struct rep_read_baton *rb;
2754 if (! rep)
2756 *contents_p = svn_stream_empty(pool);
2758 else
2760 SVN_ERR(rep_read_get_baton(&rb, fs, rep, pool));
2761 *contents_p = svn_stream_create(rb, pool);
2762 svn_stream_set_read(*contents_p, rep_read_contents);
2763 svn_stream_set_close(*contents_p, rep_read_contents_close);
2766 return SVN_NO_ERROR;
2769 svn_error_t *
2770 svn_fs_fs__get_contents(svn_stream_t **contents_p,
2771 svn_fs_t *fs,
2772 node_revision_t *noderev,
2773 apr_pool_t *pool)
2775 return read_representation(contents_p, fs, noderev->data_rep, pool);
2778 /* Baton used when reading delta windows. */
2779 struct delta_read_baton
2781 struct rep_state *rs;
2782 unsigned char checksum[APR_MD5_DIGESTSIZE];
2785 /* This implements the svn_txdelta_next_window_fn_t interface. */
2786 static svn_error_t *
2787 delta_read_next_window(svn_txdelta_window_t **window, void *baton,
2788 apr_pool_t *pool)
2790 struct delta_read_baton *drb = baton;
2792 if (drb->rs->off == drb->rs->end)
2794 *window = NULL;
2795 return SVN_NO_ERROR;
2798 SVN_ERR(read_window(window, drb->rs->chunk_index, drb->rs, pool));
2800 return SVN_NO_ERROR;
2803 /* This implements the svn_txdelta_md5_digest_fn_t interface. */
2804 static const unsigned char *
2805 delta_read_md5_digest(void *baton)
2807 struct delta_read_baton *drb = baton;
2809 return drb->checksum;
2812 svn_error_t *
2813 svn_fs_fs__get_file_delta_stream(svn_txdelta_stream_t **stream_p,
2814 svn_fs_t *fs,
2815 node_revision_t *source,
2816 node_revision_t *target,
2817 apr_pool_t *pool)
2819 svn_stream_t *source_stream, *target_stream;
2821 /* Try a shortcut: if the target is stored as a delta against the source,
2822 then just use that delta. */
2823 if (source && source->data_rep && target->data_rep)
2825 struct rep_state *rep_state;
2826 struct rep_args *rep_args;
2828 /* Read target's base rep if any. */
2829 SVN_ERR(create_rep_state(&rep_state, &rep_args, target->data_rep,
2830 fs, pool));
2831 /* If that matches source, then use this delta as is. */
2832 if (rep_args->is_delta
2833 && (rep_args->is_delta_vs_empty
2834 || (rep_args->base_revision == source->data_rep->revision
2835 && rep_args->base_offset == source->data_rep->offset)))
2837 /* Create the delta read baton. */
2838 struct delta_read_baton *drb = apr_pcalloc(pool, sizeof(*drb));
2839 drb->rs = rep_state;
2840 memcpy(drb->checksum, target->data_rep->checksum,
2841 sizeof(drb->checksum));
2842 *stream_p = svn_txdelta_stream_create(drb, delta_read_next_window,
2843 delta_read_md5_digest, pool);
2844 return SVN_NO_ERROR;
2846 else
2847 SVN_ERR(svn_io_file_close(rep_state->file, pool));
2850 /* Read both fulltexts and construct a delta. */
2851 if (source)
2852 SVN_ERR(read_representation(&source_stream, fs, source->data_rep, pool));
2853 else
2854 source_stream = svn_stream_empty(pool);
2855 SVN_ERR(read_representation(&target_stream, fs, target->data_rep, pool));
2856 svn_txdelta(stream_p, source_stream, target_stream, pool);
2858 return SVN_NO_ERROR;
2862 /* Fetch the contents of a directory into ENTRIES. Values are stored
2863 as filename to string mappings; further conversion is necessary to
2864 convert them into svn_fs_dirent_t values. */
2865 static svn_error_t *
2866 get_dir_contents(apr_hash_t *entries,
2867 svn_fs_t *fs,
2868 node_revision_t *noderev,
2869 apr_pool_t *pool)
2871 svn_stream_t *contents;
2873 if (noderev->data_rep && noderev->data_rep->txn_id)
2875 apr_file_t *dir_file;
2876 const char *filename = path_txn_node_children(fs, noderev->id, pool);
2878 /* The representation is mutable. Read the old directory
2879 contents from the mutable children file, followed by the
2880 changes we've made in this transaction. */
2881 SVN_ERR(svn_io_file_open(&dir_file, filename, APR_READ | APR_BUFFERED,
2882 APR_OS_DEFAULT, pool));
2883 contents = svn_stream_from_aprfile(dir_file, pool);
2884 SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool));
2885 SVN_ERR(svn_hash_read_incremental(entries, contents, NULL, pool));
2886 SVN_ERR(svn_io_file_close(dir_file, pool));
2888 else if (noderev->data_rep)
2890 /* The representation is immutable. Read it normally. */
2891 SVN_ERR(read_representation(&contents, fs, noderev->data_rep, pool));
2892 SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool));
2893 SVN_ERR(svn_stream_close(contents));
2896 return SVN_NO_ERROR;
2899 /* Return a copy of the directory hash ENTRIES in POOL. */
2900 static apr_hash_t *
2901 copy_dir_entries(apr_hash_t *entries,
2902 apr_pool_t *pool)
2904 apr_hash_t *new_entries = apr_hash_make(pool);
2905 apr_hash_index_t *hi;
2907 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
2909 void *val;
2910 svn_fs_dirent_t *dirent, *new_dirent;
2912 apr_hash_this(hi, NULL, NULL, &val);
2913 dirent = val;
2914 new_dirent = apr_palloc(pool, sizeof(*new_dirent));
2915 new_dirent->name = apr_pstrdup(pool, dirent->name);
2916 new_dirent->kind = dirent->kind;
2917 new_dirent->id = svn_fs_fs__id_copy(dirent->id, pool);
2918 apr_hash_set(new_entries, new_dirent->name, APR_HASH_KEY_STRING,
2919 new_dirent);
2921 return new_entries;
2925 svn_error_t *
2926 svn_fs_fs__rep_contents_dir(apr_hash_t **entries_p,
2927 svn_fs_t *fs,
2928 node_revision_t *noderev,
2929 apr_pool_t *pool)
2931 fs_fs_data_t *ffd = fs->fsap_data;
2932 apr_hash_t *unparsed_entries, *parsed_entries;
2933 apr_hash_index_t *hi;
2934 unsigned int hid;
2936 /* Calculate an index into the dir entries cache. This should be
2937 completely ignored if this is a mutable noderev. */
2938 hid = DIR_CACHE_ENTRIES_MASK(svn_fs_fs__id_rev(noderev->id));
2940 /* If we have this directory cached, return it. */
2941 if (! svn_fs_fs__id_txn_id(noderev->id) &&
2942 ffd->dir_cache_id[hid] && svn_fs_fs__id_eq(ffd->dir_cache_id[hid],
2943 noderev->id))
2945 *entries_p = copy_dir_entries(ffd->dir_cache[hid], pool);
2946 return SVN_NO_ERROR;
2949 /* Read in the directory hash. */
2950 unparsed_entries = apr_hash_make(pool);
2951 SVN_ERR(get_dir_contents(unparsed_entries, fs, noderev, pool));
2953 parsed_entries = apr_hash_make(pool);
2955 /* Translate the string dir entries into real entries. */
2956 for (hi = apr_hash_first(pool, unparsed_entries); hi; hi = apr_hash_next(hi))
2958 const void *key;
2959 void *val;
2960 char *str_val;
2961 char *str, *last_str;
2962 svn_fs_dirent_t *dirent = apr_pcalloc(pool, sizeof(*dirent));
2964 apr_hash_this(hi, &key, NULL, &val);
2965 str_val = apr_pstrdup(pool, *((char **)val));
2966 dirent->name = apr_pstrdup(pool, key);
2968 str = apr_strtok(str_val, " ", &last_str);
2969 if (str == NULL)
2970 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2971 _("Directory entry corrupt"));
2973 if (strcmp(str, KIND_FILE) == 0)
2975 dirent->kind = svn_node_file;
2977 else if (strcmp(str, KIND_DIR) == 0)
2979 dirent->kind = svn_node_dir;
2981 else
2983 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2984 _("Directory entry corrupt"));
2987 str = apr_strtok(NULL, " ", &last_str);
2988 if (str == NULL)
2989 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2990 _("Directory entry corrupt"));
2992 dirent->id = svn_fs_fs__id_parse(str, strlen(str), pool);
2994 apr_hash_set(parsed_entries, dirent->name, APR_HASH_KEY_STRING, dirent);
2997 /* If this is an immutable directory, let's cache the contents. */
2998 if (! svn_fs_fs__id_txn_id(noderev->id))
3000 /* Start by NULLing the ID field, so that we never leave the
3001 cache in an illegal state. */
3002 ffd->dir_cache_id[hid] = NULL;
3004 if (ffd->dir_cache_pool[hid])
3005 svn_pool_clear(ffd->dir_cache_pool[hid]);
3006 else
3007 ffd->dir_cache_pool[hid] = svn_pool_create(fs->pool);
3009 ffd->dir_cache[hid] = copy_dir_entries(parsed_entries,
3010 ffd->dir_cache_pool[hid]);
3011 ffd->dir_cache_id[hid] = svn_fs_fs__id_copy(noderev->id,
3012 ffd->dir_cache_pool[hid]);
3015 *entries_p = parsed_entries;
3016 return SVN_NO_ERROR;
3019 svn_error_t *
3020 svn_fs_fs__get_proplist(apr_hash_t **proplist_p,
3021 svn_fs_t *fs,
3022 node_revision_t *noderev,
3023 apr_pool_t *pool)
3025 apr_hash_t *proplist;
3026 svn_stream_t *stream;
3028 proplist = apr_hash_make(pool);
3030 if (noderev->prop_rep && noderev->prop_rep->txn_id)
3032 apr_file_t *props_file;
3033 const char *filename = path_txn_node_props(fs, noderev->id, pool);
3035 SVN_ERR(svn_io_file_open(&props_file, filename,
3036 APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
3037 pool));
3038 stream = svn_stream_from_aprfile(props_file, pool);
3039 SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
3040 SVN_ERR(svn_io_file_close(props_file, pool));
3042 else if (noderev->prop_rep)
3044 SVN_ERR(read_representation(&stream, fs, noderev->prop_rep, pool));
3045 SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
3046 SVN_ERR(svn_stream_close(stream));
3049 *proplist_p = proplist;
3051 return SVN_NO_ERROR;
3054 svn_error_t *
3055 svn_fs_fs__file_length(svn_filesize_t *length,
3056 node_revision_t *noderev,
3057 apr_pool_t *pool)
3059 if (noderev->data_rep)
3060 *length = noderev->data_rep->expanded_size;
3061 else
3062 *length = 0;
3064 return SVN_NO_ERROR;
3067 svn_boolean_t
3068 svn_fs_fs__noderev_same_rep_key(representation_t *a,
3069 representation_t *b)
3071 if (a == b)
3072 return TRUE;
3074 if (a && (! b))
3075 return FALSE;
3077 if (b && (! a))
3078 return FALSE;
3080 if (a->offset != b->offset)
3081 return FALSE;
3083 if (a->revision != b->revision)
3084 return FALSE;
3086 return TRUE;
3089 svn_error_t *
3090 svn_fs_fs__file_checksum(unsigned char digest[],
3091 node_revision_t *noderev,
3092 apr_pool_t *pool)
3094 if (noderev->data_rep)
3095 memcpy(digest, noderev->data_rep->checksum, APR_MD5_DIGESTSIZE);
3096 else
3097 memset(digest, 0, APR_MD5_DIGESTSIZE);
3099 return SVN_NO_ERROR;
3102 representation_t *
3103 svn_fs_fs__rep_copy(representation_t *rep,
3104 apr_pool_t *pool)
3106 representation_t *rep_new;
3108 if (rep == NULL)
3109 return NULL;
3111 rep_new = apr_pcalloc(pool, sizeof(*rep_new));
3113 memcpy(rep_new, rep, sizeof(*rep_new));
3115 return rep_new;
3118 /* Merge the internal-use-only CHANGE into a hash of public-FS
3119 svn_fs_path_change_t CHANGES, collapsing multiple changes into a
3120 single summarical (is that real word?) change per path. Also keep
3121 the COPYFROM_HASH up to date with new adds and replaces. */
3122 static svn_error_t *
3123 fold_change(apr_hash_t *changes,
3124 const change_t *change,
3125 apr_hash_t *copyfrom_hash)
3127 apr_pool_t *pool = apr_hash_pool_get(changes);
3128 apr_pool_t *copyfrom_pool = apr_hash_pool_get(copyfrom_hash);
3129 svn_fs_path_change_t *old_change, *new_change;
3130 const char *path, *copyfrom_string, *copyfrom_path = NULL;
3132 if ((old_change = apr_hash_get(changes, change->path, APR_HASH_KEY_STRING)))
3134 /* This path already exists in the hash, so we have to merge
3135 this change into the already existing one. */
3137 /* Get the existing copyfrom entry for this path. */
3138 copyfrom_string = apr_hash_get(copyfrom_hash, change->path,
3139 APR_HASH_KEY_STRING);
3141 /* If this entry existed in the copyfrom hash, we don't need to
3142 copy it. */
3143 if (copyfrom_string)
3144 copyfrom_path = change->path;
3146 /* Since the path already exists in the hash, we don't have to
3147 dup the allocation for the path itself. */
3148 path = change->path;
3149 /* Sanity check: only allow NULL node revision ID in the
3150 `reset' case. */
3151 if ((! change->noderev_id) && (change->kind != svn_fs_path_change_reset))
3152 return svn_error_create
3153 (SVN_ERR_FS_CORRUPT, NULL,
3154 _("Missing required node revision ID"));
3156 /* Sanity check: we should be talking about the same node
3157 revision ID as our last change except where the last change
3158 was a deletion. */
3159 if (change->noderev_id
3160 && (! svn_fs_fs__id_eq(old_change->node_rev_id, change->noderev_id))
3161 && (old_change->change_kind != svn_fs_path_change_delete))
3162 return svn_error_create
3163 (SVN_ERR_FS_CORRUPT, NULL,
3164 _("Invalid change ordering: new node revision ID "
3165 "without delete"));
3167 /* Sanity check: an add, replacement, or reset must be the first
3168 thing to follow a deletion. */
3169 if ((old_change->change_kind == svn_fs_path_change_delete)
3170 && (! ((change->kind == svn_fs_path_change_replace)
3171 || (change->kind == svn_fs_path_change_reset)
3172 || (change->kind == svn_fs_path_change_add))))
3173 return svn_error_create
3174 (SVN_ERR_FS_CORRUPT, NULL,
3175 _("Invalid change ordering: non-add change on deleted path"));
3177 /* Now, merge that change in. */
3178 switch (change->kind)
3180 case svn_fs_path_change_reset:
3181 /* A reset here will simply remove the path change from the
3182 hash. */
3183 old_change = NULL;
3184 copyfrom_string = NULL;
3185 break;
3187 case svn_fs_path_change_delete:
3188 if (old_change->change_kind == svn_fs_path_change_add)
3190 /* If the path was introduced in this transaction via an
3191 add, and we are deleting it, just remove the path
3192 altogether. */
3193 old_change = NULL;
3195 else
3197 /* A deletion overrules all previous changes. */
3198 old_change->change_kind = svn_fs_path_change_delete;
3199 old_change->text_mod = change->text_mod;
3200 old_change->prop_mod = change->prop_mod;
3202 copyfrom_string = NULL;
3203 break;
3205 case svn_fs_path_change_add:
3206 case svn_fs_path_change_replace:
3207 /* An add at this point must be following a previous delete,
3208 so treat it just like a replace. */
3209 old_change->change_kind = svn_fs_path_change_replace;
3210 old_change->node_rev_id = svn_fs_fs__id_copy(change->noderev_id,
3211 pool);
3212 old_change->text_mod = change->text_mod;
3213 old_change->prop_mod = change->prop_mod;
3214 if (change->copyfrom_rev == SVN_INVALID_REVNUM)
3215 copyfrom_string = apr_pstrdup(copyfrom_pool, "");
3216 else
3218 copyfrom_string = apr_psprintf(copyfrom_pool,
3219 "%ld %s",
3220 change->copyfrom_rev,
3221 change->copyfrom_path);
3223 break;
3225 case svn_fs_path_change_modify:
3226 default:
3227 if (change->text_mod)
3228 old_change->text_mod = TRUE;
3229 if (change->prop_mod)
3230 old_change->prop_mod = TRUE;
3231 break;
3234 /* Point our new_change to our (possibly modified) old_change. */
3235 new_change = old_change;
3237 else
3239 /* This change is new to the hash, so make a new public change
3240 structure from the internal one (in the hash's pool), and dup
3241 the path into the hash's pool, too. */
3242 new_change = apr_pcalloc(pool, sizeof(*new_change));
3243 new_change->node_rev_id = svn_fs_fs__id_copy(change->noderev_id, pool);
3244 new_change->change_kind = change->kind;
3245 new_change->text_mod = change->text_mod;
3246 new_change->prop_mod = change->prop_mod;
3247 if (change->copyfrom_rev != SVN_INVALID_REVNUM)
3249 copyfrom_string = apr_psprintf(copyfrom_pool, "%ld %s",
3250 change->copyfrom_rev,
3251 change->copyfrom_path);
3253 else
3254 copyfrom_string = apr_pstrdup(copyfrom_pool, "");
3255 path = apr_pstrdup(pool, change->path);
3258 /* Add (or update) this path. */
3259 apr_hash_set(changes, path, APR_HASH_KEY_STRING, new_change);
3261 /* If copyfrom_path is non-NULL, the key is already present in the
3262 hash, so we don't need to duplicate it in the copyfrom pool. */
3263 if (! copyfrom_path)
3265 /* If copyfrom_string is NULL, the hash entry will be deleted,
3266 so we don't need to duplicate the key in the copyfrom
3267 pool. */
3268 copyfrom_path = copyfrom_string ? apr_pstrdup(copyfrom_pool, path)
3269 : path;
3272 apr_hash_set(copyfrom_hash, copyfrom_path, APR_HASH_KEY_STRING,
3273 copyfrom_string);
3275 return SVN_NO_ERROR;
3278 /* The 256 is an arbitrary size large enough to hold the node id and the
3279 * various flags. */
3280 #define MAX_CHANGE_LINE_LEN FSFS_MAX_PATH_LEN + 256
3282 /* Read the next entry in the changes record from file FILE and store
3283 the resulting change in *CHANGE_P. If there is no next record,
3284 store NULL there. Perform all allocations from POOL. */
3285 static svn_error_t *
3286 read_change(change_t **change_p,
3287 apr_file_t *file,
3288 apr_pool_t *pool)
3290 char buf[MAX_CHANGE_LINE_LEN];
3291 apr_size_t len = sizeof(buf);
3292 change_t *change;
3293 char *str, *last_str;
3294 svn_error_t *err;
3296 /* Default return value. */
3297 *change_p = NULL;
3299 err = svn_io_read_length_line(file, buf, &len, pool);
3301 /* Check for a blank line. */
3302 if (err || (len == 0))
3304 if (err && APR_STATUS_IS_EOF(err->apr_err))
3306 svn_error_clear(err);
3307 return SVN_NO_ERROR;
3309 if ((len == 0) && (! err))
3310 return SVN_NO_ERROR;
3311 return err;
3314 change = apr_pcalloc(pool, sizeof(*change));
3316 /* Get the node-id of the change. */
3317 str = apr_strtok(buf, " ", &last_str);
3318 if (str == NULL)
3319 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3320 _("Invalid changes line in rev-file"));
3322 change->noderev_id = svn_fs_fs__id_parse(str, strlen(str), pool);
3324 /* Get the change type. */
3325 str = apr_strtok(NULL, " ", &last_str);
3326 if (str == NULL)
3327 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3328 _("Invalid changes line in rev-file"));
3330 if (strcmp(str, ACTION_MODIFY) == 0)
3332 change->kind = svn_fs_path_change_modify;
3334 else if (strcmp(str, ACTION_ADD) == 0)
3336 change->kind = svn_fs_path_change_add;
3338 else if (strcmp(str, ACTION_DELETE) == 0)
3340 change->kind = svn_fs_path_change_delete;
3342 else if (strcmp(str, ACTION_REPLACE) == 0)
3344 change->kind = svn_fs_path_change_replace;
3346 else if (strcmp(str, ACTION_RESET) == 0)
3348 change->kind = svn_fs_path_change_reset;
3350 else
3352 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3353 _("Invalid change kind in rev file"));
3356 /* Get the text-mod flag. */
3357 str = apr_strtok(NULL, " ", &last_str);
3358 if (str == NULL)
3359 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3360 _("Invalid changes line in rev-file"));
3362 if (strcmp(str, FLAG_TRUE) == 0)
3364 change->text_mod = TRUE;
3366 else if (strcmp(str, FLAG_FALSE) == 0)
3368 change->text_mod = FALSE;
3370 else
3372 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3373 _("Invalid text-mod flag in rev-file"));
3376 /* Get the prop-mod flag. */
3377 str = apr_strtok(NULL, " ", &last_str);
3378 if (str == NULL)
3379 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3380 _("Invalid changes line in rev-file"));
3382 if (strcmp(str, FLAG_TRUE) == 0)
3384 change->prop_mod = TRUE;
3386 else if (strcmp(str, FLAG_FALSE) == 0)
3388 change->prop_mod = FALSE;
3390 else
3392 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3393 _("Invalid prop-mod flag in rev-file"));
3396 /* Get the changed path. */
3397 change->path = apr_pstrdup(pool, last_str);
3400 /* Read the next line, the copyfrom line. */
3401 len = sizeof(buf);
3402 SVN_ERR(svn_io_read_length_line(file, buf, &len, pool));
3404 if (len == 0)
3406 change->copyfrom_rev = SVN_INVALID_REVNUM;
3407 change->copyfrom_path = NULL;
3409 else
3411 str = apr_strtok(buf, " ", &last_str);
3412 if (! str)
3413 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3414 _("Invalid changes line in rev-file"));
3415 change->copyfrom_rev = atol(str);
3417 if (! last_str)
3418 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3419 _("Invalid changes line in rev-file"));
3421 change->copyfrom_path = apr_pstrdup(pool, last_str);
3424 *change_p = change;
3426 return SVN_NO_ERROR;
3429 /* Fetch all the changed path entries from FILE and store then in
3430 *CHANGED_PATHS. Folding is done to remove redundant or unnecessary
3431 *data. Store a hash of paths to copyfrom revisions/paths in
3432 COPYFROM_HASH if it is non-NULL. If PREFOLDED is true, assume that
3433 the changed-path entries have already been folded (by
3434 write_final_changed_path_info) and may be out of order, so we shouldn't
3435 remove children of replaced or deleted directories. Do all
3436 allocations in POOL. */
3437 static svn_error_t *
3438 fetch_all_changes(apr_hash_t *changed_paths,
3439 apr_hash_t *copyfrom_hash,
3440 apr_file_t *file,
3441 svn_boolean_t prefolded,
3442 apr_pool_t *pool)
3444 change_t *change;
3445 apr_pool_t *iterpool = svn_pool_create(pool);
3446 apr_hash_t *my_hash;
3448 /* If we are passed a NULL copyfrom hash, manufacture one for the
3449 duration of this call. */
3450 my_hash = copyfrom_hash ? copyfrom_hash : apr_hash_make(pool);
3452 /* Read in the changes one by one, folding them into our local hash
3453 as necessary. */
3455 SVN_ERR(read_change(&change, file, iterpool));
3457 while (change)
3459 SVN_ERR(fold_change(changed_paths, change, my_hash));
3461 /* Now, if our change was a deletion or replacement, we have to
3462 blow away any changes thus far on paths that are (or, were)
3463 children of this path.
3464 ### i won't bother with another iteration pool here -- at
3465 most we talking about a few extra dups of paths into what
3466 is already a temporary subpool.
3469 if (((change->kind == svn_fs_path_change_delete)
3470 || (change->kind == svn_fs_path_change_replace))
3471 && ! prefolded)
3473 apr_hash_index_t *hi;
3475 for (hi = apr_hash_first(iterpool, changed_paths);
3477 hi = apr_hash_next(hi))
3479 /* KEY is the path. */
3480 const void *hashkey;
3481 apr_ssize_t klen;
3482 apr_hash_this(hi, &hashkey, &klen, NULL);
3484 /* If we come across our own path, ignore it. */
3485 if (strcmp(change->path, hashkey) == 0)
3486 continue;
3488 /* If we come across a child of our path, remove it. */
3489 if (svn_path_is_child(change->path, hashkey, iterpool))
3490 apr_hash_set(changed_paths, hashkey, klen, NULL);
3494 /* Clear the per-iteration subpool. */
3495 svn_pool_clear(iterpool);
3497 SVN_ERR(read_change(&change, file, iterpool));
3500 /* Destroy the per-iteration subpool. */
3501 svn_pool_destroy(iterpool);
3503 return SVN_NO_ERROR;
3506 svn_error_t *
3507 svn_fs_fs__txn_changes_fetch(apr_hash_t **changed_paths_p,
3508 svn_fs_t *fs,
3509 const char *txn_id,
3510 apr_hash_t *copyfrom_cache,
3511 apr_pool_t *pool)
3513 apr_file_t *file;
3514 apr_hash_t *changed_paths = apr_hash_make(pool);
3516 SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool),
3517 APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
3519 SVN_ERR(fetch_all_changes(changed_paths, copyfrom_cache, file, FALSE,
3520 pool));
3522 SVN_ERR(svn_io_file_close(file, pool));
3524 *changed_paths_p = changed_paths;
3526 return SVN_NO_ERROR;
3529 svn_error_t *
3530 svn_fs_fs__paths_changed(apr_hash_t **changed_paths_p,
3531 svn_fs_t *fs,
3532 svn_revnum_t rev,
3533 apr_hash_t *copyfrom_cache,
3534 apr_pool_t *pool)
3536 apr_off_t changes_offset;
3537 apr_hash_t *changed_paths;
3538 apr_file_t *revision_file;
3540 SVN_ERR(ensure_revision_exists(fs, rev, pool));
3542 SVN_ERR(svn_io_file_open(&revision_file, svn_fs_fs__path_rev(fs, rev, pool),
3543 APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
3545 SVN_ERR(get_root_changes_offset(NULL, &changes_offset, revision_file,
3546 pool));
3548 SVN_ERR(svn_io_file_seek(revision_file, APR_SET, &changes_offset, pool));
3550 changed_paths = apr_hash_make(pool);
3552 SVN_ERR(fetch_all_changes(changed_paths, copyfrom_cache, revision_file,
3553 TRUE, pool));
3555 /* Close the revision file. */
3556 SVN_ERR(svn_io_file_close(revision_file, pool));
3558 *changed_paths_p = changed_paths;
3560 return SVN_NO_ERROR;
3563 /* Copy a revision node-rev SRC into the current transaction TXN_ID in
3564 the filesystem FS. This is only used to create the root of a transaction.
3565 Allocations are from POOL. */
3566 static svn_error_t *
3567 create_new_txn_noderev_from_rev(svn_fs_t *fs,
3568 const char *txn_id,
3569 svn_fs_id_t *src,
3570 apr_pool_t *pool)
3572 node_revision_t *noderev;
3573 const char *node_id, *copy_id;
3575 SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, src, pool));
3577 if (svn_fs_fs__id_txn_id(noderev->id))
3578 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3579 _("Copying from transactions not allowed"));
3581 noderev->predecessor_id = noderev->id;
3582 noderev->predecessor_count++;
3583 noderev->copyfrom_path = NULL;
3584 noderev->copyfrom_rev = SVN_INVALID_REVNUM;
3586 /* For the transaction root, the copyroot never changes. */
3588 node_id = svn_fs_fs__id_node_id(noderev->id);
3589 copy_id = svn_fs_fs__id_copy_id(noderev->id);
3590 noderev->id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool);
3592 SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, TRUE, pool));
3594 return SVN_NO_ERROR;
3597 /* A structure used by get_and_increment_txn_key_body(). */
3598 struct get_and_increment_txn_key_baton {
3599 svn_fs_t *fs;
3600 char *txn_id;
3601 apr_pool_t *pool;
3604 /* Callback used in the implementation of create_txn_dir(). This gets
3605 the current base 36 value in PATH_TXN_CURRENT and increments it.
3606 It returns the original value by the baton. */
3607 static svn_error_t *
3608 get_and_increment_txn_key_body(void *baton, apr_pool_t *pool)
3610 struct get_and_increment_txn_key_baton *cb = baton;
3611 const char *txn_current_filename = path_txn_current(cb->fs, pool);
3612 apr_file_t *txn_current_file = NULL;
3613 const char *tmp_filename;
3614 char next_txn_id[MAX_KEY_SIZE+3];
3615 svn_error_t *err = SVN_NO_ERROR;
3616 apr_pool_t *iterpool;
3617 apr_size_t len;
3618 int i;
3620 cb->txn_id = apr_palloc(cb->pool, MAX_KEY_SIZE);
3622 iterpool = svn_pool_create(pool);
3623 for (i = 0; i < RECOVERABLE_RETRY_COUNT; ++i)
3625 svn_pool_clear(iterpool);
3627 RETRY_RECOVERABLE(err, txn_current_file,
3628 svn_io_file_open(&txn_current_file,
3629 txn_current_filename,
3630 APR_READ | APR_BUFFERED,
3631 APR_OS_DEFAULT, iterpool));
3632 len = MAX_KEY_SIZE;
3633 RETRY_RECOVERABLE(err, txn_current_file,
3634 svn_io_read_length_line(txn_current_file,
3635 cb->txn_id,
3636 &len,
3637 iterpool));
3638 IGNORE_RECOVERABLE(err, svn_io_file_close(txn_current_file,
3639 iterpool));
3641 break;
3643 if (err)
3644 return err;
3646 svn_pool_destroy(iterpool);
3648 /* Increment the key and add a trailing \n to the string so the
3649 txn-current file has a newline in it. */
3650 svn_fs_fs__next_key(cb->txn_id, &len, next_txn_id);
3651 next_txn_id[len] = '\n';
3652 ++len;
3653 next_txn_id[len] = '\0';
3655 SVN_ERR(svn_io_open_unique_file2(&txn_current_file, &tmp_filename,
3656 txn_current_filename, ".tmp",
3657 svn_io_file_del_none, pool));
3659 SVN_ERR(svn_io_file_write_full(txn_current_file,
3660 next_txn_id,
3661 len,
3662 NULL,
3663 pool));
3665 SVN_ERR(svn_io_file_flush_to_disk(txn_current_file, pool));
3667 SVN_ERR(svn_io_file_close(txn_current_file, pool));
3669 SVN_ERR(svn_fs_fs__move_into_place(tmp_filename, txn_current_filename,
3670 txn_current_filename, pool));
3672 return err;
3675 /* Create a unique directory for a transaction in FS based on revision
3676 REV. Return the ID for this transaction in *ID_P. Use a sequence
3677 value in the transaction ID to prevent reuse of transaction IDs. */
3678 static svn_error_t *
3679 create_txn_dir(const char **id_p, svn_fs_t *fs, svn_revnum_t rev,
3680 apr_pool_t *pool)
3682 struct get_and_increment_txn_key_baton cb;
3683 const char *txn_dir;
3685 /* Get the current transaction sequence value, which is a base-36
3686 number, from the txn-current file, and write an
3687 incremented value back out to the file. Place the revision
3688 number the transaction is based off into the transaction id. */
3689 cb.pool = pool;
3690 cb.fs = fs;
3691 SVN_ERR(with_txn_current_lock(fs,
3692 get_and_increment_txn_key_body,
3693 &cb,
3694 pool));
3695 *id_p = apr_psprintf(pool, "%ld-%s", rev, cb.txn_id);
3697 txn_dir = svn_path_join_many(pool,
3698 fs->path,
3699 PATH_TXNS_DIR,
3700 apr_pstrcat(pool, *id_p, PATH_EXT_TXN, NULL),
3701 NULL);
3703 SVN_ERR(svn_io_dir_make(txn_dir, APR_OS_DEFAULT, pool));
3705 return SVN_NO_ERROR;
3708 /* Create a unique directory for a transaction in FS based on revision
3709 REV. Return the ID for this transaction in *ID_P. This
3710 implementation is used in svn 1.4 and earlier repositories and is
3711 kept in 1.5 and greater to support the --pre-1.4-compatible and
3712 --pre-1.5-compatible repository creation options. Reused
3713 transaction IDs are possible with this implementation. */
3714 static svn_error_t *
3715 create_txn_dir_pre_1_5(const char **id_p, svn_fs_t *fs, svn_revnum_t rev,
3716 apr_pool_t *pool)
3718 unsigned int i;
3719 apr_pool_t *subpool;
3720 const char *unique_path, *prefix;
3722 /* Try to create directories named "<txndir>/<rev>-<uniqueifier>.txn". */
3723 prefix = svn_path_join_many(pool, fs->path, PATH_TXNS_DIR,
3724 apr_psprintf(pool, "%ld", rev), NULL);
3726 subpool = svn_pool_create(pool);
3727 for (i = 1; i <= 99999; i++)
3729 svn_error_t *err;
3731 svn_pool_clear(subpool);
3732 unique_path = apr_psprintf(subpool, "%s-%u" PATH_EXT_TXN, prefix, i);
3733 err = svn_io_dir_make(unique_path, APR_OS_DEFAULT, subpool);
3734 if (! err)
3736 /* We succeeded. Return the basename minus the ".txn" extension. */
3737 const char *name = svn_path_basename(unique_path, subpool);
3738 *id_p = apr_pstrndup(pool, name,
3739 strlen(name) - strlen(PATH_EXT_TXN));
3740 svn_pool_destroy(subpool);
3741 return SVN_NO_ERROR;
3743 if (! APR_STATUS_IS_EEXIST(err->apr_err))
3744 return err;
3745 svn_error_clear(err);
3748 return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
3749 NULL,
3750 _("Unable to create transaction directory "
3751 "in '%s' for revision %ld"),
3752 fs->path, rev);
3755 svn_error_t *
3756 svn_fs_fs__create_txn(svn_fs_txn_t **txn_p,
3757 svn_fs_t *fs,
3758 svn_revnum_t rev,
3759 apr_pool_t *pool)
3761 fs_fs_data_t *ffd = fs->fsap_data;
3762 svn_fs_txn_t *txn;
3763 svn_fs_id_t *root_id;
3765 txn = apr_pcalloc(pool, sizeof(*txn));
3767 /* Get the txn_id. */
3768 if (ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
3769 SVN_ERR(create_txn_dir(&txn->id, fs, rev, pool));
3770 else
3771 SVN_ERR(create_txn_dir_pre_1_5(&txn->id, fs, rev, pool));
3773 txn->fs = fs;
3774 txn->base_rev = rev;
3776 txn->vtable = &txn_vtable;
3777 *txn_p = txn;
3779 /* Create a new root node for this transaction. */
3780 SVN_ERR(svn_fs_fs__rev_get_root(&root_id, fs, rev, pool));
3781 SVN_ERR(create_new_txn_noderev_from_rev(fs, txn->id, root_id, pool));
3783 /* Create an empty rev file. */
3784 SVN_ERR(svn_io_file_create(path_txn_proto_rev(fs, txn->id, pool), "",
3785 pool));
3787 /* Create an empty rev-lock file. */
3788 SVN_ERR(svn_io_file_create(path_txn_proto_rev_lock(fs, txn->id, pool), "",
3789 pool));
3791 /* Create an empty changes file. */
3792 SVN_ERR(svn_io_file_create(path_txn_changes(fs, txn->id, pool), "",
3793 pool));
3795 /* Create the next-ids file. */
3796 SVN_ERR(svn_io_file_create(path_txn_next_ids(fs, txn->id, pool), "0 0\n",
3797 pool));
3799 return SVN_NO_ERROR;
3802 /* Store the property list for transaction TXN_ID in PROPLIST.
3803 Perform temporary allocations in POOL. */
3804 static svn_error_t *
3805 get_txn_proplist(apr_hash_t *proplist,
3806 svn_fs_t *fs,
3807 const char *txn_id,
3808 apr_pool_t *pool)
3810 apr_file_t *txn_prop_file;
3812 /* Open the transaction properties file. */
3813 SVN_ERR(svn_io_file_open(&txn_prop_file, path_txn_props(fs, txn_id, pool),
3814 APR_READ | APR_BUFFERED,
3815 APR_OS_DEFAULT, pool));
3817 /* Read in the property list. */
3818 SVN_ERR(svn_hash_read2(proplist,
3819 svn_stream_from_aprfile(txn_prop_file, pool),
3820 SVN_HASH_TERMINATOR, pool));
3822 SVN_ERR(svn_io_file_close(txn_prop_file, pool));
3824 return SVN_NO_ERROR;
3827 svn_error_t *
3828 svn_fs_fs__change_txn_prop(svn_fs_txn_t *txn,
3829 const char *name,
3830 const svn_string_t *value,
3831 apr_pool_t *pool)
3833 apr_array_header_t *props = apr_array_make(pool, 1, sizeof(svn_prop_t));
3834 svn_prop_t prop;
3836 prop.name = name;
3837 prop.value = value;
3838 APR_ARRAY_PUSH(props, svn_prop_t) = prop;
3840 return svn_fs_fs__change_txn_props(txn, props, pool);
3843 svn_error_t *
3844 svn_fs_fs__change_txn_props(svn_fs_txn_t *txn,
3845 apr_array_header_t *props,
3846 apr_pool_t *pool)
3848 apr_file_t *txn_prop_file;
3849 apr_hash_t *txn_prop = apr_hash_make(pool);
3850 int i;
3851 svn_error_t *err;
3853 err = get_txn_proplist(txn_prop, txn->fs, txn->id, pool);
3854 /* Here - and here only - we need to deal with the possibility that the
3855 transaction property file doesn't yet exist. The rest of the
3856 implementation assumes that the file exists, but we're called to set the
3857 initial transaction properties as the transaction is being created. */
3858 if (err && (APR_STATUS_IS_ENOENT(err->apr_err)))
3859 svn_error_clear(err);
3860 else if (err)
3861 return err;
3863 for (i = 0; i < props->nelts; i++)
3865 svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t);
3867 apr_hash_set(txn_prop, prop->name, APR_HASH_KEY_STRING, prop->value);
3870 /* Create a new version of the file and write out the new props. */
3871 /* Open the transaction properties file. */
3872 SVN_ERR(svn_io_file_open(&txn_prop_file,
3873 path_txn_props(txn->fs, txn->id, pool),
3874 APR_WRITE | APR_CREATE | APR_TRUNCATE
3875 | APR_BUFFERED, APR_OS_DEFAULT, pool));
3877 SVN_ERR(svn_hash_write(txn_prop, txn_prop_file, pool));
3879 SVN_ERR(svn_io_file_close(txn_prop_file, pool));
3881 return SVN_NO_ERROR;
3884 svn_error_t *
3885 svn_fs_fs__get_txn(transaction_t **txn_p,
3886 svn_fs_t *fs,
3887 const char *txn_id,
3888 apr_pool_t *pool)
3890 transaction_t *txn;
3891 node_revision_t *noderev;
3892 svn_fs_id_t *root_id;
3894 txn = apr_pcalloc(pool, sizeof(*txn));
3895 txn->proplist = apr_hash_make(pool);
3897 SVN_ERR(get_txn_proplist(txn->proplist, fs, txn_id, pool));
3898 root_id = svn_fs_fs__id_txn_create("0", "0", txn_id, pool);
3900 SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, root_id, pool));
3902 txn->root_id = svn_fs_fs__id_copy(noderev->id, pool);
3903 txn->base_id = svn_fs_fs__id_copy(noderev->predecessor_id, pool);
3904 txn->copies = NULL;
3906 *txn_p = txn;
3908 return SVN_NO_ERROR;
3911 /* Write out the currently available next node_id NODE_ID and copy_id
3912 COPY_ID for transaction TXN_ID in filesystem FS. Perform temporary
3913 allocations in POOL. */
3914 static svn_error_t *
3915 write_next_ids(svn_fs_t *fs,
3916 const char *txn_id,
3917 const char *node_id,
3918 const char *copy_id,
3919 apr_pool_t *pool)
3921 apr_file_t *file;
3922 svn_stream_t *out_stream;
3924 SVN_ERR(svn_io_file_open(&file, path_txn_next_ids(fs, txn_id, pool),
3925 APR_WRITE | APR_TRUNCATE,
3926 APR_OS_DEFAULT, pool));
3928 out_stream = svn_stream_from_aprfile(file, pool);
3930 SVN_ERR(svn_stream_printf(out_stream, pool, "%s %s\n", node_id, copy_id));
3932 SVN_ERR(svn_stream_close(out_stream));
3933 SVN_ERR(svn_io_file_close(file, pool));
3935 return SVN_NO_ERROR;
3938 /* Find out what the next unique node-id and copy-id are for
3939 transaction TXN_ID in filesystem FS. Store the results in *NODE_ID
3940 and *COPY_ID. Perform all allocations in POOL. */
3941 static svn_error_t *
3942 read_next_ids(const char **node_id,
3943 const char **copy_id,
3944 svn_fs_t *fs,
3945 const char *txn_id,
3946 apr_pool_t *pool)
3948 apr_file_t *file;
3949 char buf[MAX_KEY_SIZE*2+3];
3950 apr_size_t limit;
3951 char *str, *last_str;
3953 SVN_ERR(svn_io_file_open(&file, path_txn_next_ids(fs, txn_id, pool),
3954 APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
3956 limit = sizeof(buf);
3957 SVN_ERR(svn_io_read_length_line(file, buf, &limit, pool));
3959 SVN_ERR(svn_io_file_close(file, pool));
3961 /* Parse this into two separate strings. */
3963 str = apr_strtok(buf, " ", &last_str);
3964 if (! str)
3965 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3966 _("next-id file corrupt"));
3968 *node_id = apr_pstrdup(pool, str);
3970 str = apr_strtok(NULL, " ", &last_str);
3971 if (! str)
3972 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3973 _("next-id file corrupt"));
3975 *copy_id = apr_pstrdup(pool, str);
3977 return SVN_NO_ERROR;
3980 /* Get a new and unique to this transaction node-id for transaction
3981 TXN_ID in filesystem FS. Store the new node-id in *NODE_ID_P.
3982 Perform all allocations in POOL. */
3983 static svn_error_t *
3984 get_new_txn_node_id(const char **node_id_p,
3985 svn_fs_t *fs,
3986 const char *txn_id,
3987 apr_pool_t *pool)
3989 const char *cur_node_id, *cur_copy_id;
3990 char *node_id;
3991 apr_size_t len;
3993 /* First read in the current next-ids file. */
3994 SVN_ERR(read_next_ids(&cur_node_id, &cur_copy_id, fs, txn_id, pool));
3996 node_id = apr_pcalloc(pool, strlen(cur_node_id) + 2);
3998 len = strlen(cur_node_id);
3999 svn_fs_fs__next_key(cur_node_id, &len, node_id);
4001 SVN_ERR(write_next_ids(fs, txn_id, node_id, cur_copy_id, pool));
4003 *node_id_p = apr_pstrcat(pool, "_", cur_node_id, NULL);
4005 return SVN_NO_ERROR;
4008 svn_error_t *
4009 svn_fs_fs__create_node(const svn_fs_id_t **id_p,
4010 svn_fs_t *fs,
4011 node_revision_t *noderev,
4012 const char *copy_id,
4013 const char *txn_id,
4014 apr_pool_t *pool)
4016 const char *node_id;
4017 const svn_fs_id_t *id;
4019 /* Get a new node-id for this node. */
4020 SVN_ERR(get_new_txn_node_id(&node_id, fs, txn_id, pool));
4022 id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool);
4024 noderev->id = id;
4026 SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool));
4028 *id_p = id;
4030 return SVN_NO_ERROR;
4033 svn_error_t *
4034 svn_fs_fs__purge_txn(svn_fs_t *fs,
4035 const char *txn_id,
4036 apr_pool_t *pool)
4038 fs_fs_data_t *ffd = fs->fsap_data;
4040 /* Remove the shared transaction object associated with this transaction. */
4041 SVN_ERR(purge_shared_txn(fs, txn_id, pool));
4042 /* Remove the directory associated with this transaction. */
4043 SVN_ERR(svn_io_remove_dir2(path_txn_dir(fs, txn_id, pool), FALSE,
4044 NULL, NULL, pool));
4045 if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
4047 /* Delete protorev and its lock, which aren't in the txn
4048 directory. It's OK if they don't exist (for example, if this
4049 is post-commit and the proto-rev has been moved into
4050 place). */
4051 svn_error_t *err = svn_io_remove_file(path_txn_proto_rev(fs, txn_id,
4052 pool), pool);
4053 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
4055 svn_error_clear(err);
4056 err = NULL;
4058 if (err)
4059 return err;
4061 err = svn_io_remove_file(path_txn_proto_rev_lock(fs, txn_id, pool),
4062 pool);
4063 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
4065 svn_error_clear(err);
4066 err = NULL;
4068 if (err)
4069 return err;
4071 return SVN_NO_ERROR;
4075 svn_error_t *
4076 svn_fs_fs__abort_txn(svn_fs_txn_t *txn,
4077 apr_pool_t *pool)
4079 fs_fs_data_t *ffd;
4081 SVN_ERR(svn_fs__check_fs(txn->fs, TRUE));
4083 /* Clean out the directory cache. */
4084 ffd = txn->fs->fsap_data;
4085 memset(&ffd->dir_cache_id, 0,
4086 sizeof(svn_fs_id_t *) * NUM_DIR_CACHE_ENTRIES);
4088 /* Now, purge the transaction. */
4089 SVN_ERR_W(svn_fs_fs__purge_txn(txn->fs, txn->id, pool),
4090 _("Transaction cleanup failed"));
4092 return SVN_NO_ERROR;
4096 static const char *
4097 unparse_dir_entry(svn_node_kind_t kind, const svn_fs_id_t *id,
4098 apr_pool_t *pool)
4100 return apr_psprintf(pool, "%s %s",
4101 (kind == svn_node_file) ? KIND_FILE : KIND_DIR,
4102 svn_fs_fs__id_unparse(id, pool)->data);
4105 /* Given a hash ENTRIES of dirent structions, return a hash in
4106 *STR_ENTRIES_P, that has svn_string_t as the values in the format
4107 specified by the fs_fs directory contents file. Perform
4108 allocations in POOL. */
4109 static svn_error_t *
4110 unparse_dir_entries(apr_hash_t **str_entries_p,
4111 apr_hash_t *entries,
4112 apr_pool_t *pool)
4114 apr_hash_index_t *hi;
4116 *str_entries_p = apr_hash_make(pool);
4118 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
4120 const void *key;
4121 apr_ssize_t klen;
4122 void *val;
4123 svn_fs_dirent_t *dirent;
4124 const char *new_val;
4126 apr_hash_this(hi, &key, &klen, &val);
4127 dirent = val;
4128 new_val = unparse_dir_entry(dirent->kind, dirent->id, pool);
4129 apr_hash_set(*str_entries_p, key, klen,
4130 svn_string_create(new_val, pool));
4133 return SVN_NO_ERROR;
4137 svn_error_t *
4138 svn_fs_fs__set_entry(svn_fs_t *fs,
4139 const char *txn_id,
4140 node_revision_t *parent_noderev,
4141 const char *name,
4142 const svn_fs_id_t *id,
4143 svn_node_kind_t kind,
4144 apr_pool_t *pool)
4146 representation_t *rep = parent_noderev->data_rep;
4147 const char *filename = path_txn_node_children(fs, parent_noderev->id, pool);
4148 apr_file_t *file;
4149 svn_stream_t *out;
4151 if (!rep || !rep->txn_id)
4154 apr_hash_t *entries;
4155 apr_pool_t *subpool = svn_pool_create(pool);
4157 /* Before we can modify the directory, we need to dump its old
4158 contents into a mutable representation file. */
4159 SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, parent_noderev,
4160 subpool));
4161 SVN_ERR(unparse_dir_entries(&entries, entries, subpool));
4162 SVN_ERR(svn_io_file_open(&file, filename,
4163 APR_WRITE | APR_CREATE | APR_BUFFERED,
4164 APR_OS_DEFAULT, pool));
4165 out = svn_stream_from_aprfile(file, pool);
4166 SVN_ERR(svn_hash_write2(entries, out, SVN_HASH_TERMINATOR, subpool));
4168 svn_pool_destroy(subpool);
4171 /* Mark the node-rev's data rep as mutable. */
4172 rep = apr_pcalloc(pool, sizeof(*rep));
4173 rep->revision = SVN_INVALID_REVNUM;
4174 rep->txn_id = txn_id;
4175 parent_noderev->data_rep = rep;
4176 SVN_ERR(svn_fs_fs__put_node_revision(fs, parent_noderev->id,
4177 parent_noderev, FALSE, pool));
4179 else
4181 /* The directory rep is already mutable, so just open it for append. */
4182 SVN_ERR(svn_io_file_open(&file, filename, APR_WRITE | APR_APPEND,
4183 APR_OS_DEFAULT, pool));
4184 out = svn_stream_from_aprfile(file, pool);
4187 /* Append an incremental hash entry for the entry change. */
4188 if (id)
4190 const char *val = unparse_dir_entry(kind, id, pool);
4192 SVN_ERR(svn_stream_printf(out, pool, "K %" APR_SIZE_T_FMT "\n%s\n"
4193 "V %" APR_SIZE_T_FMT "\n%s\n",
4194 strlen(name), name,
4195 strlen(val), val));
4197 else
4199 SVN_ERR(svn_stream_printf(out, pool, "D %" APR_SIZE_T_FMT "\n%s\n",
4200 strlen(name), name));
4203 SVN_ERR(svn_io_file_close(file, pool));
4204 return SVN_NO_ERROR;
4207 /* Write a single change entry, path PATH, change CHANGE, and copyfrom
4208 string COPYFROM, into the file specified by FILE. All temporary
4209 allocations are in POOL. */
4210 static svn_error_t *
4211 write_change_entry(apr_file_t *file,
4212 const char *path,
4213 svn_fs_path_change_t *change,
4214 const char *copyfrom,
4215 apr_pool_t *pool)
4217 const char *idstr, *buf;
4218 const char *change_string = NULL;
4220 switch (change->change_kind)
4222 case svn_fs_path_change_modify:
4223 change_string = ACTION_MODIFY;
4224 break;
4225 case svn_fs_path_change_add:
4226 change_string = ACTION_ADD;
4227 break;
4228 case svn_fs_path_change_delete:
4229 change_string = ACTION_DELETE;
4230 break;
4231 case svn_fs_path_change_replace:
4232 change_string = ACTION_REPLACE;
4233 break;
4234 case svn_fs_path_change_reset:
4235 change_string = ACTION_RESET;
4236 break;
4237 default:
4238 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
4239 _("Invalid change type"));
4242 if (change->node_rev_id)
4243 idstr = svn_fs_fs__id_unparse(change->node_rev_id, pool)->data;
4244 else
4245 idstr = ACTION_RESET;
4247 buf = apr_psprintf(pool, "%s %s %s %s %s\n",
4248 idstr, change_string,
4249 change->text_mod ? FLAG_TRUE : FLAG_FALSE,
4250 change->prop_mod ? FLAG_TRUE : FLAG_FALSE,
4251 path);
4253 SVN_ERR(svn_io_file_write_full(file, buf, strlen(buf), NULL, pool));
4255 if (copyfrom)
4257 SVN_ERR(svn_io_file_write_full(file, copyfrom, strlen(copyfrom),
4258 NULL, pool));
4261 SVN_ERR(svn_io_file_write_full(file, "\n", 1, NULL, pool));
4263 return SVN_NO_ERROR;
4266 svn_error_t *
4267 svn_fs_fs__add_change(svn_fs_t *fs,
4268 const char *txn_id,
4269 const char *path,
4270 const svn_fs_id_t *id,
4271 svn_fs_path_change_kind_t change_kind,
4272 svn_boolean_t text_mod,
4273 svn_boolean_t prop_mod,
4274 svn_revnum_t copyfrom_rev,
4275 const char *copyfrom_path,
4276 apr_pool_t *pool)
4278 apr_file_t *file;
4279 const char *copyfrom;
4280 svn_fs_path_change_t *change = apr_pcalloc(pool, sizeof(*change));
4282 SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool),
4283 APR_APPEND | APR_WRITE | APR_CREATE
4284 | APR_BUFFERED, APR_OS_DEFAULT, pool));
4286 if (copyfrom_rev != SVN_INVALID_REVNUM)
4287 copyfrom = apr_psprintf(pool, "%ld %s", copyfrom_rev, copyfrom_path);
4288 else
4289 copyfrom = "";
4291 change->node_rev_id = id;
4292 change->change_kind = change_kind;
4293 change->text_mod = text_mod;
4294 change->prop_mod = prop_mod;
4296 SVN_ERR(write_change_entry(file, path, change, copyfrom, pool));
4298 SVN_ERR(svn_io_file_close(file, pool));
4300 return SVN_NO_ERROR;
4303 /* This baton is used by the representation writing streams. It keeps
4304 track of the checksum information as well as the total size of the
4305 representation so far. */
4306 struct rep_write_baton
4308 /* The FS we are writing to. */
4309 svn_fs_t *fs;
4311 /* Actual file to which we are writing. */
4312 svn_stream_t *rep_stream;
4314 /* A stream from the delta combiner. Data written here gets
4315 deltified, then eventually written to rep_stream. */
4316 svn_stream_t *delta_stream;
4318 /* Where is this representation header stored. */
4319 apr_off_t rep_offset;
4321 /* Start of the actual data. */
4322 apr_off_t delta_start;
4324 /* How many bytes have been written to this rep already. */
4325 svn_filesize_t rep_size;
4327 /* The node revision for which we're writing out info. */
4328 node_revision_t *noderev;
4330 /* Actual output file. */
4331 apr_file_t *file;
4332 /* Lock 'cookie' used to unlock the output file once we've finished
4333 writing to it. */
4334 void *lockcookie;
4336 struct apr_md5_ctx_t md5_context;
4338 apr_pool_t *pool;
4340 apr_pool_t *parent_pool;
4343 /* Handler for the write method of the representation writable stream.
4344 BATON is a rep_write_baton, DATA is the data to write, and *LEN is
4345 the length of this data. */
4346 static svn_error_t *
4347 rep_write_contents(void *baton,
4348 const char *data,
4349 apr_size_t *len)
4351 struct rep_write_baton *b = baton;
4353 apr_md5_update(&b->md5_context, data, *len);
4354 b->rep_size += *len;
4356 /* If we are writing a delta, use that stream. */
4357 if (b->delta_stream)
4359 SVN_ERR(svn_stream_write(b->delta_stream, data, len));
4361 else
4363 SVN_ERR(svn_stream_write(b->rep_stream, data, len));
4366 return SVN_NO_ERROR;
4369 /* Given a node-revision NODEREV in filesystem FS, return the
4370 representation in *REP to use as the base for a text representation
4371 delta. Perform temporary allocations in *POOL. */
4372 static svn_error_t *
4373 choose_delta_base(representation_t **rep,
4374 svn_fs_t *fs,
4375 node_revision_t *noderev,
4376 apr_pool_t *pool)
4378 int count;
4379 node_revision_t *base;
4381 /* If we have no predecessors, then use the empty stream as a
4382 base. */
4383 if (! noderev->predecessor_count)
4385 *rep = NULL;
4386 return SVN_NO_ERROR;
4389 /* Flip the rightmost '1' bit of the predecessor count to determine
4390 which file rev (counting from 0) we want to use. (To see why
4391 count & (count - 1) unsets the rightmost set bit, think about how
4392 you decrement a binary number.) */
4393 count = noderev->predecessor_count;
4394 count = count & (count - 1);
4396 /* Walk back a number of predecessors equal to the difference
4397 between count and the original predecessor count. (For example,
4398 if noderev has ten predecessors and we want the eighth file rev,
4399 walk back two predecessors.) */
4400 base = noderev;
4401 while ((count++) < noderev->predecessor_count)
4402 SVN_ERR(svn_fs_fs__get_node_revision(&base, fs,
4403 base->predecessor_id, pool));
4405 *rep = base->data_rep;
4407 return SVN_NO_ERROR;
4410 /* Get a rep_write_baton and store it in *WB_P for the representation
4411 indicated by NODEREV in filesystem FS. Perform allocations in
4412 POOL. Only appropriate for file contents, not for props or
4413 directory contents. */
4414 static svn_error_t *
4415 rep_write_get_baton(struct rep_write_baton **wb_p,
4416 svn_fs_t *fs,
4417 node_revision_t *noderev,
4418 apr_pool_t *pool)
4420 struct rep_write_baton *b;
4421 apr_file_t *file;
4422 representation_t *base_rep;
4423 svn_stream_t *source;
4424 const char *header;
4425 svn_txdelta_window_handler_t wh;
4426 void *whb;
4427 fs_fs_data_t *ffd = fs->fsap_data;
4429 b = apr_pcalloc(pool, sizeof(*b));
4431 apr_md5_init(&(b->md5_context));
4433 b->fs = fs;
4434 b->parent_pool = pool;
4435 b->pool = svn_pool_create(pool);
4436 b->rep_size = 0;
4437 b->noderev = noderev;
4439 /* Open the prototype rev file and seek to its end. */
4440 SVN_ERR(get_writable_proto_rev(&file, &b->lockcookie,
4441 fs, svn_fs_fs__id_txn_id(noderev->id),
4442 b->pool));
4444 b->file = file;
4445 b->rep_stream = svn_stream_from_aprfile(file, b->pool);
4447 SVN_ERR(get_file_offset(&b->rep_offset, file, b->pool));
4449 /* Get the base for this delta. */
4450 SVN_ERR(choose_delta_base(&base_rep, fs, noderev, b->pool));
4451 SVN_ERR(read_representation(&source, fs, base_rep, b->pool));
4453 /* Write out the rep header. */
4454 if (base_rep)
4456 header = apr_psprintf(b->pool, REP_DELTA " %ld %" APR_OFF_T_FMT " %"
4457 SVN_FILESIZE_T_FMT "\n",
4458 base_rep->revision, base_rep->offset,
4459 base_rep->size);
4461 else
4463 header = REP_DELTA "\n";
4465 SVN_ERR(svn_io_file_write_full(file, header, strlen(header), NULL,
4466 b->pool));
4468 /* Now determine the offset of the actual svndiff data. */
4469 SVN_ERR(get_file_offset(&b->delta_start, file, b->pool));
4471 /* Prepare to write the svndiff data. */
4472 if (ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT)
4473 svn_txdelta_to_svndiff2(&wh, &whb, b->rep_stream, 1, pool);
4474 else
4475 svn_txdelta_to_svndiff2(&wh, &whb, b->rep_stream, 0, pool);
4477 b->delta_stream = svn_txdelta_target_push(wh, whb, source, b->pool);
4479 *wb_p = b;
4481 return SVN_NO_ERROR;
4484 /* Close handler for the representation write stream. BATON is a
4485 rep_write_baton. Writes out a new node-rev that correctly
4486 references the representation we just finished writing. */
4487 static svn_error_t *
4488 rep_write_contents_close(void *baton)
4490 struct rep_write_baton *b = baton;
4491 representation_t *rep;
4492 apr_off_t offset;
4494 rep = apr_pcalloc(b->parent_pool, sizeof(*rep));
4495 rep->offset = b->rep_offset;
4497 /* Close our delta stream so the last bits of svndiff are written
4498 out. */
4499 if (b->delta_stream)
4500 SVN_ERR(svn_stream_close(b->delta_stream));
4502 /* Determine the length of the svndiff data. */
4503 SVN_ERR(get_file_offset(&offset, b->file, b->pool));
4504 rep->size = offset - b->delta_start;
4506 /* Fill in the rest of the representation field. */
4507 rep->expanded_size = b->rep_size;
4508 rep->txn_id = svn_fs_fs__id_txn_id(b->noderev->id);
4509 rep->revision = SVN_INVALID_REVNUM;
4511 /* Finalize the MD5 checksum. */
4512 apr_md5_final(rep->checksum, &b->md5_context);
4514 /* Write out our cosmetic end marker. */
4515 SVN_ERR(svn_stream_printf(b->rep_stream, b->pool, "ENDREP\n"));
4517 b->noderev->data_rep = rep;
4519 /* Write out the new node-rev information. */
4520 SVN_ERR(svn_fs_fs__put_node_revision(b->fs, b->noderev->id, b->noderev, FALSE,
4521 b->pool));
4523 SVN_ERR(svn_io_file_close(b->file, b->pool));
4524 SVN_ERR(unlock_proto_rev(b->fs, rep->txn_id, b->lockcookie, b->pool));
4525 svn_pool_destroy(b->pool);
4527 return SVN_NO_ERROR;
4530 /* Store a writable stream in *CONTENTS_P that will receive all data
4531 written and store it as the file data representation referenced by
4532 NODEREV in filesystem FS. Perform temporary allocations in
4533 POOL. Only appropriate for file data, not props or directory
4534 contents. */
4535 static svn_error_t *
4536 set_representation(svn_stream_t **contents_p,
4537 svn_fs_t *fs,
4538 node_revision_t *noderev,
4539 apr_pool_t *pool)
4541 struct rep_write_baton *wb;
4543 if (! svn_fs_fs__id_txn_id(noderev->id))
4544 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
4545 _("Attempted to write to non-transaction"));
4547 SVN_ERR(rep_write_get_baton(&wb, fs, noderev, pool));
4549 *contents_p = svn_stream_create(wb, pool);
4550 svn_stream_set_write(*contents_p, rep_write_contents);
4551 svn_stream_set_close(*contents_p, rep_write_contents_close);
4553 return SVN_NO_ERROR;
4556 svn_error_t *
4557 svn_fs_fs__set_contents(svn_stream_t **stream,
4558 svn_fs_t *fs,
4559 node_revision_t *noderev,
4560 apr_pool_t *pool)
4562 if (noderev->kind != svn_node_file)
4563 return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL,
4564 _("Can't set text contents of a directory"));
4566 return set_representation(stream, fs, noderev, pool);
4569 svn_error_t *
4570 svn_fs_fs__create_successor(const svn_fs_id_t **new_id_p,
4571 svn_fs_t *fs,
4572 const svn_fs_id_t *old_idp,
4573 node_revision_t *new_noderev,
4574 const char *copy_id,
4575 const char *txn_id,
4576 apr_pool_t *pool)
4578 const svn_fs_id_t *id;
4580 if (! copy_id)
4581 copy_id = svn_fs_fs__id_copy_id(old_idp);
4582 id = svn_fs_fs__id_txn_create(svn_fs_fs__id_node_id(old_idp), copy_id,
4583 txn_id, pool);
4585 new_noderev->id = id;
4587 if (! new_noderev->copyroot_path)
4589 new_noderev->copyroot_path = apr_pstrdup(pool,
4590 new_noderev->created_path);
4591 new_noderev->copyroot_rev = svn_fs_fs__id_rev(new_noderev->id);
4594 SVN_ERR(svn_fs_fs__put_node_revision(fs, new_noderev->id, new_noderev, FALSE,
4595 pool));
4597 *new_id_p = id;
4599 return SVN_NO_ERROR;
4602 svn_error_t *
4603 svn_fs_fs__set_proplist(svn_fs_t *fs,
4604 node_revision_t *noderev,
4605 apr_hash_t *proplist,
4606 apr_pool_t *pool)
4608 const char *filename = path_txn_node_props(fs, noderev->id, pool);
4609 apr_file_t *file;
4610 svn_stream_t *out;
4612 /* Dump the property list to the mutable property file. */
4613 SVN_ERR(svn_io_file_open(&file, filename,
4614 APR_WRITE | APR_CREATE | APR_TRUNCATE
4615 | APR_BUFFERED, APR_OS_DEFAULT, pool));
4616 out = svn_stream_from_aprfile(file, pool);
4617 SVN_ERR(svn_hash_write2(proplist, out, SVN_HASH_TERMINATOR, pool));
4618 SVN_ERR(svn_io_file_close(file, pool));
4620 /* Mark the node-rev's prop rep as mutable, if not already done. */
4621 if (!noderev->prop_rep || !noderev->prop_rep->txn_id)
4623 noderev->prop_rep = apr_pcalloc(pool, sizeof(*noderev->prop_rep));
4624 noderev->prop_rep->txn_id = svn_fs_fs__id_txn_id(noderev->id);
4625 SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool));
4628 return SVN_NO_ERROR;
4631 /* Read the 'current' file for filesystem FS and store the next
4632 available node id in *NODE_ID, and the next available copy id in
4633 *COPY_ID. Allocations are performed from POOL. */
4634 static svn_error_t *
4635 get_next_revision_ids(const char **node_id,
4636 const char **copy_id,
4637 svn_fs_t *fs,
4638 apr_pool_t *pool)
4640 char *buf;
4641 char *str, *last_str;
4643 SVN_ERR(read_current(svn_fs_fs__path_current(fs, pool), &buf, pool));
4645 str = apr_strtok(buf, " ", &last_str);
4646 if (! str)
4647 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
4648 _("Corrupt current file"));
4650 str = apr_strtok(NULL, " ", &last_str);
4651 if (! str)
4652 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
4653 _("Corrupt current file"));
4655 *node_id = apr_pstrdup(pool, str);
4657 str = apr_strtok(NULL, " ", &last_str);
4658 if (! str)
4659 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
4660 _("Corrupt current file"));
4662 *copy_id = apr_pstrdup(pool, str);
4664 return SVN_NO_ERROR;
4667 /* This baton is used by the stream created for write_hash_rep. */
4668 struct write_hash_baton
4670 svn_stream_t *stream;
4672 apr_size_t size;
4674 struct apr_md5_ctx_t md5_context;
4677 /* The handler for the write_hash_rep stream. BATON is a
4678 write_hash_baton, DATA has the data to write and *LEN is the number
4679 of bytes to write. */
4680 static svn_error_t *
4681 write_hash_handler(void *baton,
4682 const char *data,
4683 apr_size_t *len)
4685 struct write_hash_baton *whb = baton;
4687 apr_md5_update(&whb->md5_context, data, *len);
4689 SVN_ERR(svn_stream_write(whb->stream, data, len));
4690 whb->size += *len;
4692 return SVN_NO_ERROR;
4695 /* Write out the hash HASH as a text representation to file FILE. In
4696 the process, record the total size of the dump in *SIZE, and the
4697 md5 digest in CHECKSUM. Perform temporary allocations in POOL. */
4698 static svn_error_t *
4699 write_hash_rep(svn_filesize_t *size,
4700 unsigned char checksum[APR_MD5_DIGESTSIZE],
4701 apr_file_t *file,
4702 apr_hash_t *hash,
4703 apr_pool_t *pool)
4705 svn_stream_t *stream;
4706 struct write_hash_baton *whb;
4708 whb = apr_pcalloc(pool, sizeof(*whb));
4710 whb->stream = svn_stream_from_aprfile(file, pool);
4711 whb->size = 0;
4712 apr_md5_init(&(whb->md5_context));
4714 stream = svn_stream_create(whb, pool);
4715 svn_stream_set_write(stream, write_hash_handler);
4717 SVN_ERR(svn_stream_printf(whb->stream, pool, "PLAIN\n"));
4719 SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool));
4721 /* Store the results. */
4722 apr_md5_final(checksum, &whb->md5_context);
4723 *size = whb->size;
4725 SVN_ERR(svn_stream_printf(whb->stream, pool, "ENDREP\n"));
4727 return SVN_NO_ERROR;
4730 /* Copy a node-revision specified by id ID in fileystem FS from a
4731 transaction into the permanent rev-file FILE. Return the offset of
4732 the new node-revision in *OFFSET. If this is a directory, all
4733 children are copied as well. START_NODE_ID and START_COPY_ID are
4734 the first available node and copy ids for this filesystem, for older
4735 FS formats. Temporary allocations are from POOL. */
4736 static svn_error_t *
4737 write_final_rev(const svn_fs_id_t **new_id_p,
4738 apr_file_t *file,
4739 svn_revnum_t rev,
4740 svn_fs_t *fs,
4741 const svn_fs_id_t *id,
4742 const char *start_node_id,
4743 const char *start_copy_id,
4744 apr_pool_t *pool)
4746 node_revision_t *noderev;
4747 apr_off_t my_offset;
4748 char my_node_id_buf[MAX_KEY_SIZE + 2];
4749 char my_copy_id_buf[MAX_KEY_SIZE + 2];
4750 const svn_fs_id_t *new_id;
4751 const char *node_id, *copy_id, *my_node_id, *my_copy_id;
4752 fs_fs_data_t *ffd = fs->fsap_data;
4754 *new_id_p = NULL;
4756 /* Check to see if this is a transaction node. */
4757 if (! svn_fs_fs__id_txn_id(id))
4758 return SVN_NO_ERROR;
4760 SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool));
4762 if (noderev->kind == svn_node_dir)
4764 apr_pool_t *subpool;
4765 apr_hash_t *entries, *str_entries;
4766 svn_fs_dirent_t *dirent;
4767 void *val;
4768 apr_hash_index_t *hi;
4770 /* This is a directory. Write out all the children first. */
4771 subpool = svn_pool_create(pool);
4773 SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev, pool));
4775 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
4777 svn_pool_clear(subpool);
4778 apr_hash_this(hi, NULL, NULL, &val);
4779 dirent = val;
4780 SVN_ERR(write_final_rev(&new_id, file, rev, fs, dirent->id,
4781 start_node_id, start_copy_id,
4782 subpool));
4783 if (new_id && (svn_fs_fs__id_rev(new_id) == rev))
4784 dirent->id = svn_fs_fs__id_copy(new_id, pool);
4786 svn_pool_destroy(subpool);
4788 if (noderev->data_rep && noderev->data_rep->txn_id)
4790 /* Write out the contents of this directory as a text rep. */
4791 SVN_ERR(unparse_dir_entries(&str_entries, entries, pool));
4793 noderev->data_rep->txn_id = NULL;
4794 noderev->data_rep->revision = rev;
4795 SVN_ERR(get_file_offset(&noderev->data_rep->offset, file, pool));
4796 SVN_ERR(write_hash_rep(&noderev->data_rep->size,
4797 noderev->data_rep->checksum, file,
4798 str_entries, pool));
4799 noderev->data_rep->expanded_size = noderev->data_rep->size;
4802 else
4804 /* This is a file. We should make sure the data rep, if it
4805 exists in a "this" state, gets rewritten to our new revision
4806 num. */
4808 if (noderev->data_rep && noderev->data_rep->txn_id)
4810 noderev->data_rep->txn_id = NULL;
4811 noderev->data_rep->revision = rev;
4815 /* Fix up the property reps. */
4816 if (noderev->prop_rep && noderev->prop_rep->txn_id)
4818 apr_hash_t *proplist;
4820 SVN_ERR(svn_fs_fs__get_proplist(&proplist, fs, noderev, pool));
4821 SVN_ERR(get_file_offset(&noderev->prop_rep->offset, file, pool));
4822 SVN_ERR(write_hash_rep(&noderev->prop_rep->size,
4823 noderev->prop_rep->checksum, file,
4824 proplist, pool));
4826 noderev->prop_rep->txn_id = NULL;
4827 noderev->prop_rep->revision = rev;
4831 /* Convert our temporary ID into a permanent revision one. */
4832 SVN_ERR(get_file_offset(&my_offset, file, pool));
4834 node_id = svn_fs_fs__id_node_id(noderev->id);
4835 if (*node_id == '_')
4837 if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
4838 my_node_id = apr_psprintf(pool, "%s-%ld", node_id + 1, rev);
4839 else
4841 svn_fs_fs__add_keys(start_node_id, node_id + 1, my_node_id_buf);
4842 my_node_id = my_node_id_buf;
4845 else
4846 my_node_id = node_id;
4848 copy_id = svn_fs_fs__id_copy_id(noderev->id);
4849 if (*copy_id == '_')
4851 if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
4852 my_copy_id = apr_psprintf(pool, "%s-%ld", copy_id + 1, rev);
4853 else
4855 svn_fs_fs__add_keys(start_copy_id, copy_id + 1, my_copy_id_buf);
4856 my_copy_id = my_copy_id_buf;
4859 else
4860 my_copy_id = copy_id;
4862 if (noderev->copyroot_rev == SVN_INVALID_REVNUM)
4863 noderev->copyroot_rev = rev;
4865 new_id = svn_fs_fs__id_rev_create(my_node_id, my_copy_id, rev, my_offset,
4866 pool);
4868 noderev->id = new_id;
4870 /* Write out our new node-revision. */
4871 SVN_ERR(write_noderev_txn(file, noderev,
4872 svn_fs_fs__fs_supports_mergeinfo(fs),
4873 pool));
4875 /* Return our ID that references the revision file. */
4876 *new_id_p = noderev->id;
4878 return SVN_NO_ERROR;
4881 /* Write the changed path info from transaction TXN_ID in filesystem
4882 FS to the permanent rev-file FILE. *OFFSET_P is set the to offset
4883 in the file of the beginning of this information. Perform
4884 temporary allocations in POOL. */
4885 static svn_error_t *
4886 write_final_changed_path_info(apr_off_t *offset_p,
4887 apr_file_t *file,
4888 svn_fs_t *fs,
4889 const char *txn_id,
4890 apr_pool_t *pool)
4892 const char *copyfrom;
4893 apr_hash_t *changed_paths, *copyfrom_cache = apr_hash_make(pool);
4894 apr_off_t offset;
4895 apr_hash_index_t *hi;
4896 apr_pool_t *iterpool = svn_pool_create(pool);
4898 SVN_ERR(get_file_offset(&offset, file, pool));
4900 SVN_ERR(svn_fs_fs__txn_changes_fetch(&changed_paths, fs, txn_id,
4901 copyfrom_cache, pool));
4903 /* Iterate through the changed paths one at a time, and convert the
4904 temporary node-id into a permanent one for each change entry. */
4905 for (hi = apr_hash_first(pool, changed_paths); hi; hi = apr_hash_next(hi))
4907 node_revision_t *noderev;
4908 const svn_fs_id_t *id;
4909 svn_fs_path_change_t *change;
4910 const void *key;
4911 void *val;
4913 svn_pool_clear(iterpool);
4915 apr_hash_this(hi, &key, NULL, &val);
4916 change = val;
4918 id = change->node_rev_id;
4920 /* If this was a delete of a mutable node, then it is OK to
4921 leave the change entry pointing to the non-existent temporary
4922 node, since it will never be used. */
4923 if ((change->change_kind != svn_fs_path_change_delete) &&
4924 (! svn_fs_fs__id_txn_id(id)))
4926 SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, iterpool));
4928 /* noderev has the permanent node-id at this point, so we just
4929 substitute it for the temporary one. */
4930 change->node_rev_id = noderev->id;
4933 /* Find the cached copyfrom information. */
4934 copyfrom = apr_hash_get(copyfrom_cache, key, APR_HASH_KEY_STRING);
4936 /* Write out the new entry into the final rev-file. */
4937 SVN_ERR(write_change_entry(file, key, change, copyfrom, iterpool));
4940 svn_pool_destroy(iterpool);
4942 *offset_p = offset;
4944 return SVN_NO_ERROR;
4948 svn_error_t *
4949 svn_fs_fs__dup_perms(const char *filename,
4950 const char *perms_reference,
4951 apr_pool_t *pool)
4953 #ifndef WIN32
4954 apr_status_t status;
4955 apr_finfo_t finfo;
4956 const char *filename_apr, *perms_reference_apr;
4958 SVN_ERR(svn_path_cstring_from_utf8(&filename_apr, filename, pool));
4959 SVN_ERR(svn_path_cstring_from_utf8(&perms_reference_apr, perms_reference,
4960 pool));
4962 status = apr_stat(&finfo, perms_reference_apr, APR_FINFO_PROT, pool);
4963 if (status)
4964 return svn_error_wrap_apr(status, _("Can't stat '%s'"),
4965 svn_path_local_style(perms_reference, pool));
4966 status = apr_file_perms_set(filename_apr, finfo.protection);
4967 if (status)
4968 return svn_error_wrap_apr(status, _("Can't chmod '%s'"),
4969 svn_path_local_style(filename, pool));
4970 #endif
4971 return SVN_NO_ERROR;
4975 svn_error_t *
4976 svn_fs_fs__move_into_place(const char *old_filename,
4977 const char *new_filename,
4978 const char *perms_reference,
4979 apr_pool_t *pool)
4981 svn_error_t *err;
4983 SVN_ERR(svn_fs_fs__dup_perms(old_filename, perms_reference, pool));
4985 /* Move the file into place. */
4986 err = svn_io_file_rename(old_filename, new_filename, pool);
4987 if (err && APR_STATUS_IS_EXDEV(err->apr_err))
4989 apr_file_t *file;
4991 /* Can't rename across devices; fall back to copying. */
4992 svn_error_clear(err);
4993 err = SVN_NO_ERROR;
4994 SVN_ERR(svn_io_copy_file(old_filename, new_filename, TRUE, pool));
4996 /* Flush the target of the copy to disk. */
4997 SVN_ERR(svn_io_file_open(&file, new_filename, APR_READ,
4998 APR_OS_DEFAULT, pool));
4999 SVN_ERR(svn_io_file_flush_to_disk(file, pool));
5000 SVN_ERR(svn_io_file_close(file, pool));
5002 if (err)
5003 return err;
5005 #ifdef __linux__
5007 /* Linux has the unusual feature that fsync() on a file is not
5008 enough to ensure that a file's directory entries have been
5009 flushed to disk; you have to fsync the directory as well.
5010 On other operating systems, we'd only be asking for trouble
5011 by trying to open and fsync a directory. */
5012 const char *dirname;
5013 apr_file_t *file;
5015 dirname = svn_path_dirname(new_filename, pool);
5016 SVN_ERR(svn_io_file_open(&file, dirname, APR_READ, APR_OS_DEFAULT,
5017 pool));
5018 SVN_ERR(svn_io_file_flush_to_disk(file, pool));
5019 SVN_ERR(svn_io_file_close(file, pool));
5021 #endif
5023 return SVN_NO_ERROR;
5026 /* Atomically update the current file to hold the specifed REV,
5027 NEXT_NODE_ID, and NEXT_COPY_ID. (The two next-ID parameters are
5028 ignored and may be NULL if the FS format does not use them.)
5029 Perform temporary allocations in POOL. */
5030 static svn_error_t *
5031 write_current(svn_fs_t *fs, svn_revnum_t rev, const char *next_node_id,
5032 const char *next_copy_id, apr_pool_t *pool)
5034 char *buf;
5035 const char *tmp_name, *name;
5036 apr_file_t *file;
5037 fs_fs_data_t *ffd = fs->fsap_data;
5039 /* Now we can just write out this line. */
5040 if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
5041 buf = apr_psprintf(pool, "%ld\n", rev);
5042 else
5043 buf = apr_psprintf(pool, "%ld %s %s\n", rev, next_node_id, next_copy_id);
5045 name = svn_fs_fs__path_current(fs, pool);
5046 SVN_ERR(svn_io_open_unique_file2(&file, &tmp_name, name, ".tmp",
5047 svn_io_file_del_none, pool));
5049 SVN_ERR(svn_io_file_write_full(file, buf, strlen(buf), NULL, pool));
5051 SVN_ERR(svn_io_file_flush_to_disk(file, pool));
5053 SVN_ERR(svn_io_file_close(file, pool));
5055 SVN_ERR(svn_fs_fs__move_into_place(tmp_name, name, name, pool));
5057 return SVN_NO_ERROR;
5060 /* Update the current file to hold the correct next node and copy_ids
5061 from transaction TXN_ID in filesystem FS. The current revision is
5062 set to REV. Perform temporary allocations in POOL. */
5063 static svn_error_t *
5064 write_final_current(svn_fs_t *fs,
5065 const char *txn_id,
5066 svn_revnum_t rev,
5067 const char *start_node_id,
5068 const char *start_copy_id,
5069 apr_pool_t *pool)
5071 const char *txn_node_id, *txn_copy_id;
5072 char new_node_id[MAX_KEY_SIZE + 2];
5073 char new_copy_id[MAX_KEY_SIZE + 2];
5074 fs_fs_data_t *ffd = fs->fsap_data;
5076 if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
5077 return write_current(fs, rev, NULL, NULL, pool);
5079 /* To find the next available ids, we add the id that used to be in
5080 the current file, to the next ids from the transaction file. */
5081 SVN_ERR(read_next_ids(&txn_node_id, &txn_copy_id, fs, txn_id, pool));
5083 svn_fs_fs__add_keys(start_node_id, txn_node_id, new_node_id);
5084 svn_fs_fs__add_keys(start_copy_id, txn_copy_id, new_copy_id);
5086 return write_current(fs, rev, new_node_id, new_copy_id, pool);
5089 /* Verify that the user registed with FS has all the locks necessary to
5090 permit all the changes associate with TXN_NAME.
5091 The FS write lock is assumed to be held by the caller. */
5092 static svn_error_t *
5093 verify_locks(svn_fs_t *fs,
5094 const char *txn_name,
5095 apr_pool_t *pool)
5097 apr_pool_t *subpool = svn_pool_create(pool);
5098 apr_hash_t *changes;
5099 apr_hash_index_t *hi;
5100 apr_array_header_t *changed_paths;
5101 svn_stringbuf_t *last_recursed = NULL;
5102 int i;
5104 /* Fetch the changes for this transaction. */
5105 SVN_ERR(svn_fs_fs__txn_changes_fetch(&changes, fs, txn_name, NULL, pool));
5107 /* Make an array of the changed paths, and sort them depth-first-ily. */
5108 changed_paths = apr_array_make(pool, apr_hash_count(changes) + 1,
5109 sizeof(const char *));
5110 for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
5112 const void *key;
5113 apr_hash_this(hi, &key, NULL, NULL);
5114 APR_ARRAY_PUSH(changed_paths, const char *) = key;
5116 qsort(changed_paths->elts, changed_paths->nelts,
5117 changed_paths->elt_size, svn_sort_compare_paths);
5119 /* Now, traverse the array of changed paths, verify locks. Note
5120 that if we need to do a recursive verification a path, we'll skip
5121 over children of that path when we get to them. */
5122 for (i = 0; i < changed_paths->nelts; i++)
5124 const char *path;
5125 svn_fs_path_change_t *change;
5126 svn_boolean_t recurse = TRUE;
5128 svn_pool_clear(subpool);
5129 path = APR_ARRAY_IDX(changed_paths, i, const char *);
5131 /* If this path has already been verified as part of a recursive
5132 check of one of its parents, no need to do it again. */
5133 if (last_recursed
5134 && svn_path_is_child(last_recursed->data, path, subpool))
5135 continue;
5137 /* Fetch the change associated with our path. */
5138 change = apr_hash_get(changes, path, APR_HASH_KEY_STRING);
5140 /* What does it mean to succeed at lock verification for a given
5141 path? For an existing file or directory getting modified
5142 (text, props), it means we hold the lock on the file or
5143 directory. For paths being added or removed, we need to hold
5144 the locks for that path and any children of that path.
5146 WHEW! We have no reliable way to determine the node kind
5147 of deleted items, but fortunately we are going to do a
5148 recursive check on deleted paths regardless of their kind. */
5149 if (change->change_kind == svn_fs_path_change_modify)
5150 recurse = FALSE;
5151 SVN_ERR(svn_fs_fs__allow_locked_operation(path, fs, recurse, TRUE,
5152 subpool));
5154 /* If we just did a recursive check, remember the path we
5155 checked (so children can be skipped). */
5156 if (recurse)
5158 if (! last_recursed)
5159 last_recursed = svn_stringbuf_create(path, pool);
5160 else
5161 svn_stringbuf_set(last_recursed, path);
5164 svn_pool_destroy(subpool);
5165 return SVN_NO_ERROR;
5168 /* Baton used for commit_body below. */
5169 struct commit_baton {
5170 svn_revnum_t *new_rev_p;
5171 svn_fs_t *fs;
5172 svn_fs_txn_t *txn;
5175 /* The work-horse for svn_fs_fs__commit, called with the FS write lock.
5176 This implements the svn_fs_fs__with_write_lock() 'body' callback
5177 type. BATON is a 'struct commit_baton *'. */
5178 static svn_error_t *
5179 commit_body(void *baton, apr_pool_t *pool)
5181 struct commit_baton *cb = baton;
5182 fs_fs_data_t *ffd = cb->fs->fsap_data;
5183 const char *old_rev_filename, *rev_filename, *proto_filename;
5184 const char *revprop_filename, *final_revprop;
5185 const svn_fs_id_t *root_id, *new_root_id;
5186 const char *start_node_id = NULL, *start_copy_id = NULL;
5187 svn_revnum_t old_rev, new_rev;
5188 apr_file_t *proto_file;
5189 void *proto_file_lockcookie;
5190 apr_off_t changed_path_offset;
5191 char *buf;
5192 apr_hash_t *txnprops;
5193 svn_string_t date;
5195 /* Get the current youngest revision. */
5196 SVN_ERR(svn_fs_fs__youngest_rev(&old_rev, cb->fs, pool));
5198 /* Check to make sure this transaction is based off the most recent
5199 revision. */
5200 if (cb->txn->base_rev != old_rev)
5201 return svn_error_create(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL,
5202 _("Transaction out of date"));
5204 /* Locks may have been added (or stolen) between the calling of
5205 previous svn_fs.h functions and svn_fs_commit_txn(), so we need
5206 to re-examine every changed-path in the txn and re-verify all
5207 discovered locks. */
5208 SVN_ERR(verify_locks(cb->fs, cb->txn->id, pool));
5210 /* Get the next node_id and copy_id to use. */
5211 if (ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
5212 SVN_ERR(get_next_revision_ids(&start_node_id, &start_copy_id, cb->fs,
5213 pool));
5215 /* We are going to be one better than this puny old revision. */
5216 new_rev = old_rev + 1;
5218 /* Get a write handle on the proto revision file. */
5219 SVN_ERR(get_writable_proto_rev(&proto_file, &proto_file_lockcookie,
5220 cb->fs, cb->txn->id, pool));
5222 /* Write out all the node-revisions and directory contents. */
5223 root_id = svn_fs_fs__id_txn_create("0", "0", cb->txn->id, pool);
5224 SVN_ERR(write_final_rev(&new_root_id, proto_file, new_rev, cb->fs, root_id,
5225 start_node_id, start_copy_id,
5226 pool));
5228 /* Write the changed-path information. */
5229 SVN_ERR(write_final_changed_path_info(&changed_path_offset, proto_file,
5230 cb->fs, cb->txn->id, pool));
5232 /* Write the final line. */
5233 buf = apr_psprintf(pool, "\n%" APR_OFF_T_FMT " %" APR_OFF_T_FMT "\n",
5234 svn_fs_fs__id_offset(new_root_id),
5235 changed_path_offset);
5236 SVN_ERR(svn_io_file_write_full(proto_file, buf, strlen(buf), NULL,
5237 pool));
5238 SVN_ERR(svn_io_file_flush_to_disk(proto_file, pool));
5239 SVN_ERR(svn_io_file_close(proto_file, pool));
5241 /* We don't unlock the prototype revision file immediately to avoid a
5242 race with another caller writing to the prototype revision file
5243 before we commit it. */
5245 /* Remove any temporary txn props representing 'flags'. */
5246 SVN_ERR(svn_fs_fs__txn_proplist(&txnprops, cb->txn, pool));
5247 if (txnprops)
5249 apr_array_header_t *props = apr_array_make(pool, 3, sizeof(svn_prop_t));
5250 svn_prop_t prop;
5251 prop.value = NULL;
5253 if (apr_hash_get(txnprops, SVN_FS__PROP_TXN_CHECK_OOD,
5254 APR_HASH_KEY_STRING))
5256 prop.name = SVN_FS__PROP_TXN_CHECK_OOD;
5257 APR_ARRAY_PUSH(props, svn_prop_t) = prop;
5260 if (apr_hash_get(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS,
5261 APR_HASH_KEY_STRING))
5263 prop.name = SVN_FS__PROP_TXN_CHECK_LOCKS;
5264 APR_ARRAY_PUSH(props, svn_prop_t) = prop;
5267 if (! apr_is_empty_array(props))
5268 SVN_ERR(svn_fs_fs__change_txn_props(cb->txn, props, pool));
5271 /* Create the shard for the rev and revprop file, if we're sharding and
5272 this is the first revision of a new shard. We don't care if this
5273 fails because the shard already existed for some reason. */
5274 if (ffd->max_files_per_dir && new_rev % ffd->max_files_per_dir == 0)
5276 svn_error_t *err;
5277 err = svn_io_dir_make(path_rev_shard(cb->fs, new_rev, pool),
5278 APR_OS_DEFAULT, pool);
5279 if (err && APR_STATUS_IS_EEXIST(err->apr_err))
5280 svn_error_clear(err);
5281 else
5282 SVN_ERR(err);
5283 err = svn_io_dir_make(path_revprops_shard(cb->fs, new_rev, pool),
5284 APR_OS_DEFAULT, pool);
5285 if (err && APR_STATUS_IS_EEXIST(err->apr_err))
5286 svn_error_clear(err);
5287 else
5288 SVN_ERR(err);
5291 /* Move the finished rev file into place. */
5292 old_rev_filename = svn_fs_fs__path_rev(cb->fs, old_rev, pool);
5293 rev_filename = svn_fs_fs__path_rev(cb->fs, new_rev, pool);
5294 proto_filename = path_txn_proto_rev(cb->fs, cb->txn->id, pool);
5295 SVN_ERR(svn_fs_fs__move_into_place(proto_filename, rev_filename,
5296 old_rev_filename, pool));
5298 /* Now that we've moved the prototype revision file out of the way,
5299 we can unlock it (since further attempts to write to the file
5300 will fail as it no longer exists). We must do this so that we can
5301 remove the transaction directory later. */
5302 SVN_ERR(unlock_proto_rev(cb->fs, cb->txn->id, proto_file_lockcookie, pool));
5304 /* Update commit time to ensure that svn:date revprops remain ordered. */
5305 date.data = svn_time_to_cstring(apr_time_now(), pool);
5306 date.len = strlen(date.data);
5308 SVN_ERR(svn_fs_fs__change_txn_prop(cb->txn, SVN_PROP_REVISION_DATE,
5309 &date, pool));
5311 /* Move the revprops file into place. */
5312 revprop_filename = path_txn_props(cb->fs, cb->txn->id, pool);
5313 final_revprop = path_revprops(cb->fs, new_rev, pool);
5314 SVN_ERR(svn_fs_fs__move_into_place(revprop_filename, final_revprop,
5315 old_rev_filename, pool));
5317 /* Update the 'current' file. */
5318 SVN_ERR(write_final_current(cb->fs, cb->txn->id, new_rev, start_node_id,
5319 start_copy_id, pool));
5320 ffd->youngest_rev_cache = new_rev;
5322 /* Remove this transaction directory. */
5323 SVN_ERR(svn_fs_fs__purge_txn(cb->fs, cb->txn->id, pool));
5325 *cb->new_rev_p = new_rev;
5327 return SVN_NO_ERROR;
5330 svn_error_t *
5331 svn_fs_fs__commit(svn_revnum_t *new_rev_p,
5332 svn_fs_t *fs,
5333 svn_fs_txn_t *txn,
5334 apr_pool_t *pool)
5336 struct commit_baton cb;
5338 cb.new_rev_p = new_rev_p;
5339 cb.fs = fs;
5340 cb.txn = txn;
5341 return svn_fs_fs__with_write_lock(fs, commit_body, &cb, pool);
5344 svn_error_t *
5345 svn_fs_fs__reserve_copy_id(const char **copy_id_p,
5346 svn_fs_t *fs,
5347 const char *txn_id,
5348 apr_pool_t *pool)
5350 const char *cur_node_id, *cur_copy_id;
5351 char *copy_id;
5352 apr_size_t len;
5354 /* First read in the current next-ids file. */
5355 SVN_ERR(read_next_ids(&cur_node_id, &cur_copy_id, fs, txn_id, pool));
5357 copy_id = apr_pcalloc(pool, strlen(cur_copy_id) + 2);
5359 len = strlen(cur_copy_id);
5360 svn_fs_fs__next_key(cur_copy_id, &len, copy_id);
5362 SVN_ERR(write_next_ids(fs, txn_id, cur_node_id, copy_id, pool));
5364 *copy_id_p = apr_pstrcat(pool, "_", cur_copy_id, NULL);
5366 return SVN_NO_ERROR;
5369 /* Write out the zeroth revision for filesystem FS. */
5370 static svn_error_t *
5371 write_revision_zero(svn_fs_t *fs)
5373 apr_hash_t *proplist;
5374 svn_string_t date;
5376 /* Write out a rev file for revision 0. */
5377 SVN_ERR(svn_io_file_create(svn_fs_fs__path_rev(fs, 0, fs->pool),
5378 "PLAIN\nEND\nENDREP\n"
5379 "id: 0.0.r0/17\n"
5380 "type: dir\n"
5381 "count: 0\n"
5382 "text: 0 0 4 4 "
5383 "2d2977d1c96f487abe4a1e202dd03b4e\n"
5384 "cpath: /\n"
5385 "\n\n17 107\n", fs->pool));
5387 /* Set a date on revision 0. */
5388 date.data = svn_time_to_cstring(apr_time_now(), fs->pool);
5389 date.len = strlen(date.data);
5390 proplist = apr_hash_make(fs->pool);
5391 apr_hash_set(proplist, SVN_PROP_REVISION_DATE, APR_HASH_KEY_STRING, &date);
5392 return svn_fs_fs__set_revision_proplist(fs, 0, proplist, fs->pool);
5395 svn_error_t *
5396 svn_fs_fs__create(svn_fs_t *fs,
5397 const char *path,
5398 apr_pool_t *pool)
5400 int format = SVN_FS_FS__FORMAT_NUMBER;
5401 fs_fs_data_t *ffd = fs->fsap_data;
5403 fs->path = apr_pstrdup(pool, path);
5404 /* See if we had an explicitly requested pre-1.4- or pre-1.5-compatible. */
5405 if (fs->config)
5407 if (apr_hash_get(fs->config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE,
5408 APR_HASH_KEY_STRING))
5409 format = 1;
5410 else if (apr_hash_get(fs->config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE,
5411 APR_HASH_KEY_STRING))
5412 format = 2;
5414 ffd->format = format;
5416 /* Override the default linear layout if this is a new-enough format. */
5417 if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
5418 ffd->max_files_per_dir = SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR;
5420 if (ffd->max_files_per_dir)
5422 SVN_ERR(svn_io_make_dir_recursively(path_rev_shard(fs, 0, pool),
5423 pool));
5424 SVN_ERR(svn_io_make_dir_recursively(path_revprops_shard(fs, 0, pool),
5425 pool));
5427 else
5429 SVN_ERR(svn_io_make_dir_recursively(svn_path_join(path, PATH_REVS_DIR,
5430 pool),
5431 pool));
5432 SVN_ERR(svn_io_make_dir_recursively(svn_path_join(path,
5433 PATH_REVPROPS_DIR,
5434 pool),
5435 pool));
5437 SVN_ERR(svn_io_make_dir_recursively(svn_path_join(path, PATH_TXNS_DIR,
5438 pool),
5439 pool));
5441 if (format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
5442 SVN_ERR(svn_io_make_dir_recursively(svn_path_join(path, PATH_TXN_PROTOS_DIR,
5443 pool),
5444 pool));
5446 SVN_ERR(svn_io_file_create(svn_fs_fs__path_current(fs, pool),
5447 (format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT
5448 ? "0\n" : "0 1 1\n"),
5449 pool));
5450 SVN_ERR(svn_io_file_create(path_lock(fs, pool), "", pool));
5451 SVN_ERR(svn_fs_fs__set_uuid(fs, svn_uuid_generate(pool), pool));
5453 SVN_ERR(write_revision_zero(fs));
5455 /* Create the txn-current file if the repository supports
5456 the transaction sequence file. */
5457 if (format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
5459 SVN_ERR(svn_io_file_create(path_txn_current(fs, pool),
5460 "0\n", pool));
5461 SVN_ERR(svn_io_file_create(path_txn_current_lock(fs, pool),
5462 "", pool));
5465 /* This filesystem is ready. Stamp it with a format number. */
5466 SVN_ERR(write_format(path_format(fs, pool),
5467 ffd->format, ffd->max_files_per_dir, FALSE, pool));
5469 ffd->youngest_rev_cache = 0;
5470 return SVN_NO_ERROR;
5473 /* Part of the recovery procedure. Return the largest revision *REV in
5474 filesystem FS. Use POOL for temporary allocation. */
5475 static svn_error_t *
5476 recover_get_largest_revision(svn_fs_t *fs, svn_revnum_t *rev, apr_pool_t *pool)
5478 /* Discovering the largest revision in the filesystem would be an
5479 expensive operation if we did a readdir() or searched linearly,
5480 so we'll do a form of binary search. left is a revision that we
5481 know exists, right a revision that we know does not exist. */
5482 apr_pool_t *iterpool;
5483 svn_revnum_t left, right = 1;
5485 iterpool = svn_pool_create(pool);
5486 /* Keep doubling right, until we find a revision that doesn't exist. */
5487 while (1)
5489 svn_node_kind_t kind;
5490 SVN_ERR(svn_io_check_path(svn_fs_fs__path_rev(fs, right, iterpool),
5491 &kind, iterpool));
5492 svn_pool_clear(iterpool);
5494 if (kind == svn_node_none)
5495 break;
5497 right <<= 1;
5500 left = right >> 1;
5502 /* We know that left exists and right doesn't. Do a normal bsearch to find
5503 the last revision. */
5504 while (left + 1 < right)
5506 svn_revnum_t probe = left + ((right - left) / 2);
5507 svn_node_kind_t kind;
5509 SVN_ERR(svn_io_check_path(svn_fs_fs__path_rev(fs, probe, iterpool),
5510 &kind, iterpool));
5511 svn_pool_clear(iterpool);
5513 if (kind == svn_node_none)
5514 right = probe;
5515 else
5516 left = probe;
5519 svn_pool_destroy(iterpool);
5521 /* left is now the largest revision that exists. */
5522 *rev = left;
5523 return SVN_NO_ERROR;
5526 /* A baton for reading a fixed amount from an open file. For
5527 recover_find_max_ids() below. */
5528 struct recover_read_from_file_baton
5530 apr_file_t *file;
5531 apr_pool_t *pool;
5532 apr_size_t remaining;
5535 /* A stream read handler used by recover_find_max_ids() below.
5536 Read and return at most BATON->REMAINING bytes from the stream,
5537 returning nothing after that to indicate EOF. */
5538 static svn_error_t *
5539 read_handler_recover(void *baton, char *buffer, apr_size_t *len)
5541 struct recover_read_from_file_baton *b = baton;
5542 apr_size_t bytes_to_read = *len;
5544 if (b->remaining == 0)
5546 /* Return a successful read of zero bytes to signal EOF. */
5547 *len = 0;
5548 return SVN_NO_ERROR;
5551 if (bytes_to_read > b->remaining)
5552 bytes_to_read = b->remaining;
5553 b->remaining -= bytes_to_read;
5555 return svn_io_file_read_full(b->file, buffer, bytes_to_read, len, b->pool);
5558 /* Part of the recovery procedure. Read the directory noderev at offset
5559 OFFSET of file REV_FILE (the revision file of revision REV of
5560 filesystem FS), and set MAX_NODE_ID and MAX_COPY_ID to be the node-id
5561 and copy-id of that node, if greater than the current value stored
5562 in either. Recurse into any child directories that were modified in
5563 this revision.
5565 MAX_NODE_ID and MAX_COPY_ID must be arrays of at least MAX_KEY_SIZE.
5567 Perform temporary allocation in POOL. */
5568 static svn_error_t *
5569 recover_find_max_ids(svn_fs_t *fs, svn_revnum_t rev,
5570 apr_file_t *rev_file, apr_off_t offset,
5571 char *max_node_id, char *max_copy_id,
5572 apr_pool_t *pool)
5574 apr_hash_t *headers;
5575 char *value;
5576 node_revision_t noderev;
5577 struct rep_args *ra;
5578 struct recover_read_from_file_baton baton;
5579 svn_stream_t *stream;
5580 apr_hash_t *entries;
5581 apr_hash_index_t *hi;
5582 apr_pool_t *iterpool;
5584 SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
5585 SVN_ERR(read_header_block(&headers, rev_file, pool));
5587 /* We're going to populate a skeletal noderev - just the id and data_rep. */
5588 value = apr_hash_get(headers, HEADER_ID, APR_HASH_KEY_STRING);
5589 noderev.id = svn_fs_fs__id_parse(value, strlen(value), pool);
5591 /* Check that this is a directory. It should be. */
5592 value = apr_hash_get(headers, HEADER_TYPE, APR_HASH_KEY_STRING);
5593 if (value == NULL || strcmp(value, KIND_DIR) != 0)
5594 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
5595 _("Recovery encountered a non-directory node"));
5597 /* Get the data location. No data location indicates an empty directory. */
5598 value = apr_hash_get(headers, HEADER_TEXT, APR_HASH_KEY_STRING);
5599 if (!value)
5600 return SVN_NO_ERROR;
5601 SVN_ERR(read_rep_offsets(&noderev.data_rep, value, NULL, FALSE, pool));
5603 /* If the directory's data representation wasn't changed in this revision,
5604 we've already scanned the directory's contents for noderevs, so we don't
5605 need to again. This will occur if a property is changed on a directory
5606 without changing the directory's contents. */
5607 if (noderev.data_rep->revision != rev)
5608 return SVN_NO_ERROR;
5610 /* We could use get_dir_contents(), but this is much cheaper. It does
5611 rely on directory entries being stored as PLAIN reps, though. */
5612 offset = noderev.data_rep->offset;
5613 SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
5614 SVN_ERR(read_rep_line(&ra, rev_file, pool));
5615 if (ra->is_delta)
5616 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
5617 _("Recovery encountered a deltified directory "
5618 "representation"));
5620 /* Now create a stream that's allowed to read only as much data as is
5621 stored in the representation. */
5622 baton.file = rev_file;
5623 baton.pool = pool;
5624 baton.remaining = noderev.data_rep->expanded_size;
5625 stream = svn_stream_create(&baton, pool);
5626 svn_stream_set_read(stream, read_handler_recover);
5628 /* Now read the entries from that stream. */
5629 entries = apr_hash_make(pool);
5630 SVN_ERR(svn_hash_read2(entries, stream, SVN_HASH_TERMINATOR, pool));
5631 SVN_ERR(svn_stream_close(stream));
5633 /* Now check each of the entries in our directory to find new node and
5634 copy ids, and recurse into new subdirectories. */
5635 iterpool = svn_pool_create(pool);
5636 for (hi = apr_hash_first(NULL, entries); hi; hi = apr_hash_next(hi))
5638 void *val;
5639 char *str_val;
5640 char *str, *last_str;
5641 svn_node_kind_t kind;
5642 svn_fs_id_t *id;
5643 const char *node_id, *copy_id;
5644 apr_off_t child_dir_offset;
5646 svn_pool_clear(iterpool);
5648 apr_hash_this(hi, NULL, NULL, &val);
5649 str_val = apr_pstrdup(iterpool, *((char **)val));
5651 str = apr_strtok(str_val, " ", &last_str);
5652 if (str == NULL)
5653 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
5654 _("Directory entry corrupt"));
5656 if (strcmp(str, KIND_FILE) == 0)
5657 kind = svn_node_file;
5658 else if (strcmp(str, KIND_DIR) == 0)
5659 kind = svn_node_dir;
5660 else
5662 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
5663 _("Directory entry corrupt"));
5666 str = apr_strtok(NULL, " ", &last_str);
5667 if (str == NULL)
5668 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
5669 _("Directory entry corrupt"));
5671 id = svn_fs_fs__id_parse(str, strlen(str), iterpool);
5673 if (svn_fs_fs__id_rev(id) != rev)
5675 /* If the node wasn't modified in this revision, we've already
5676 checked the node and copy id. */
5677 continue;
5680 node_id = svn_fs_fs__id_node_id(id);
5681 copy_id = svn_fs_fs__id_copy_id(id);
5683 if (svn_fs_fs__key_compare(node_id, max_node_id) > 0)
5684 strcpy(max_node_id, node_id);
5685 if (svn_fs_fs__key_compare(copy_id, max_copy_id) > 0)
5686 strcpy(max_copy_id, copy_id);
5688 if (kind == svn_node_file)
5689 continue;
5691 child_dir_offset = svn_fs_fs__id_offset(id);
5692 SVN_ERR(recover_find_max_ids(fs, rev, rev_file, child_dir_offset,
5693 max_node_id, max_copy_id, iterpool));
5695 svn_pool_destroy(iterpool);
5697 return SVN_NO_ERROR;
5700 /* Baton used for recover_body below. */
5701 struct recover_baton {
5702 svn_fs_t *fs;
5703 svn_cancel_func_t cancel_func;
5704 void *cancel_baton;
5707 /* The work-horse for svn_fs_fs__recover, called with the FS
5708 write lock. This implements the svn_fs_fs__with_write_lock()
5709 'body' callback type. BATON is a 'struct recover_baton *'. */
5710 static svn_error_t *
5711 recover_body(void *baton, apr_pool_t *pool)
5713 struct recover_baton *b = baton;
5714 svn_fs_t *fs = b->fs;
5715 fs_fs_data_t *ffd = fs->fsap_data;
5716 svn_revnum_t max_rev;
5717 char next_node_id_buf[MAX_KEY_SIZE], next_copy_id_buf[MAX_KEY_SIZE];
5718 char *next_node_id = NULL, *next_copy_id = NULL;
5719 svn_revnum_t youngest_rev;
5720 svn_node_kind_t youngest_revprops_kind;
5722 /* First, we need to know the largest revision in the filesystem. */
5723 SVN_ERR(recover_get_largest_revision(fs, &max_rev, pool));
5725 /* Get the expected youngest revision */
5726 SVN_ERR(get_youngest(&youngest_rev, fs->path, pool));
5728 /* Policy note:
5730 Since the revprops file is written after the revs file, the true
5731 maximum available revision is the youngest one for which both are
5732 present. That's probably the same as the max_rev we just found,
5733 but if it's not, we could, in theory, repeatedly decrement
5734 max_rev until we find a revision that has both a revs and
5735 revprops file, then write db/current with that.
5737 But we choose not to. If a repository is so corrupt that it's
5738 missing at least one revprops file, we shouldn't assume that the
5739 youngest revision for which both the revs and revprops files are
5740 present is healthy. In other words, we're willing to recover
5741 from a missing or out-of-date db/current file, because db/current
5742 is truly redundant -- it's basically a cache so we don't have to
5743 find max_rev each time, albeit a cache with unusual semantics,
5744 since it also officially defines when a revision goes live. But
5745 if we're missing more than the cache, it's time to back out and
5746 let the admin reconstruct things by hand: correctness at that
5747 point may depend on external things like checking a commit email
5748 list, looking in particular working copies, etc.
5750 This policy matches well with a typical naive backup scenario.
5751 Say you're rsyncing your FSFS repository nightly to the same
5752 location. Once revs and revprops are written, you've got the
5753 maximum rev; if the backup should bomb before db/current is
5754 written, then db/current could stay arbitrarily out-of-date, but
5755 we can still recover. It's a small window, but we might as well
5756 do what we can. */
5758 /* Even if db/current were missing, it would be created with 0 by
5759 get_youngest(), so this conditional remains valid. */
5760 if (youngest_rev > max_rev)
5761 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
5762 _("Expected current rev to be <= %ld "
5763 "but found %ld"), max_rev, youngest_rev);
5765 /* We only need to search for maximum IDs for old FS formats which
5766 se global ID counters. */
5767 if (ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
5769 /* Next we need to find the maximum node id and copy id in use across the
5770 filesystem. Unfortunately, the only way we can get this information
5771 is to scan all the noderevs of all the revisions and keep track as
5772 we go along. */
5773 svn_revnum_t rev;
5774 apr_pool_t *iterpool = svn_pool_create(pool);
5775 char max_node_id[MAX_KEY_SIZE] = "0", max_copy_id[MAX_KEY_SIZE] = "0";
5776 apr_size_t len;
5778 for (rev = 0; rev <= max_rev; rev++)
5780 apr_file_t *rev_file;
5781 apr_off_t root_offset;
5783 svn_pool_clear(iterpool);
5785 if (b->cancel_func)
5786 SVN_ERR(b->cancel_func(b->cancel_baton));
5788 SVN_ERR(svn_io_file_open(&rev_file,
5789 svn_fs_fs__path_rev(fs, rev, iterpool),
5790 APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
5791 iterpool));
5792 SVN_ERR(get_root_changes_offset(&root_offset, NULL, rev_file,
5793 iterpool));
5794 SVN_ERR(recover_find_max_ids(fs, rev, rev_file, root_offset,
5795 max_node_id, max_copy_id, iterpool));
5797 svn_pool_destroy(iterpool);
5799 /* Now that we finally have the maximum revision, node-id and copy-id, we
5800 can bump the two ids to get the next of each. */
5801 len = strlen(max_node_id);
5802 svn_fs_fs__next_key(max_node_id, &len, next_node_id_buf);
5803 next_node_id = next_node_id_buf;
5804 len = strlen(max_copy_id);
5805 svn_fs_fs__next_key(max_copy_id, &len, next_copy_id_buf);
5806 next_copy_id = next_copy_id_buf;
5809 /* Before setting current, verify that there is a revprops file
5810 for the youngest revision. (Issue #2992) */
5811 SVN_ERR(svn_io_check_path(path_revprops(fs, max_rev, pool),
5812 &youngest_revprops_kind, pool));
5813 if (youngest_revprops_kind == svn_node_none)
5814 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
5815 _("Revision %ld has a revs file but no "
5816 "revprops file"),
5817 max_rev);
5818 else if (youngest_revprops_kind != svn_node_file)
5819 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
5820 _("Revision %ld has a non-file where its "
5821 "revprops file should be"),
5822 max_rev);
5824 /* Now store the discovered youngest revision, and the next IDs if
5825 relevant, in a new current file. */
5826 SVN_ERR(write_current(fs, max_rev, next_node_id, next_copy_id, pool));
5828 return SVN_NO_ERROR;
5831 /* This implements the fs_library_vtable_t.recover() API. */
5832 svn_error_t *
5833 svn_fs_fs__recover(svn_fs_t *fs,
5834 svn_cancel_func_t cancel_func, void *cancel_baton,
5835 apr_pool_t *pool)
5837 struct recover_baton b;
5839 /* We have no way to take out an exclusive lock in FSFS, so we're
5840 restricted as to the types of recovery we can do. Luckily,
5841 we just want to recreate the current file, and we can do that just
5842 by blocking other writers. */
5843 b.fs = fs;
5844 b.cancel_func = cancel_func;
5845 b.cancel_baton = cancel_baton;
5846 return svn_fs_fs__with_write_lock(fs, recover_body, &b, pool);
5849 svn_error_t *
5850 svn_fs_fs__get_uuid(svn_fs_t *fs,
5851 const char **uuid_p,
5852 apr_pool_t *pool)
5854 fs_fs_data_t *ffd = fs->fsap_data;
5856 *uuid_p = apr_pstrdup(pool, ffd->uuid);
5857 return SVN_NO_ERROR;
5860 svn_error_t *
5861 svn_fs_fs__set_uuid(svn_fs_t *fs,
5862 const char *uuid,
5863 apr_pool_t *pool)
5865 apr_file_t *uuid_file;
5866 const char *tmp_path;
5867 const char *uuid_path = path_uuid(fs, pool);
5868 fs_fs_data_t *ffd = fs->fsap_data;
5870 SVN_ERR(svn_io_open_unique_file2(&uuid_file, &tmp_path, uuid_path,
5871 ".tmp", svn_io_file_del_none, pool));
5873 if (! uuid)
5874 uuid = svn_uuid_generate(pool);
5876 SVN_ERR(svn_io_file_write_full(uuid_file, uuid, strlen(uuid), NULL,
5877 pool));
5878 SVN_ERR(svn_io_file_write_full(uuid_file, "\n", 1, NULL, pool));
5880 SVN_ERR(svn_io_file_close(uuid_file, pool));
5882 /* We use the permissions of the 'current' file, because the 'uuid'
5883 file does not exist during repository creation. */
5884 SVN_ERR(svn_fs_fs__move_into_place(tmp_path, uuid_path,
5885 svn_fs_fs__path_current(fs, pool), pool));
5887 ffd->uuid = apr_pstrdup(fs->pool, uuid);
5889 return SVN_NO_ERROR;
5892 /** Node origin lazy cache. */
5894 /* If directory PATH does not exist, create it and give it the same
5895 permissions as FS->path.*/
5896 svn_error_t *
5897 svn_fs_fs__ensure_dir_exists(const char *path,
5898 svn_fs_t *fs,
5899 apr_pool_t *pool)
5901 svn_error_t *err = svn_io_dir_make(path, APR_OS_DEFAULT, pool);
5902 if (err && APR_STATUS_IS_EEXIST(err->apr_err))
5904 svn_error_clear(err);
5905 return SVN_NO_ERROR;
5907 SVN_ERR(err);
5909 /* We successfully created a new directory. Dup the permissions
5910 from FS->path. */
5911 SVN_ERR(svn_fs_fs__dup_perms(path, fs->path, pool));
5913 return SVN_NO_ERROR;
5916 /* Set *NODE_ORIGINS to a hash mapping 'const char *' node IDs to
5917 'svn_string_t *' node revision IDs. Use POOL for allocations. */
5918 static svn_error_t *
5919 get_node_origins_from_file(svn_fs_t *fs,
5920 apr_hash_t **node_origins,
5921 const char *node_origins_file,
5922 apr_pool_t *pool)
5924 apr_file_t *fd;
5925 svn_error_t *err;
5926 svn_stream_t *stream;
5928 *node_origins = NULL;
5929 err = svn_io_file_open(&fd, node_origins_file,
5930 APR_READ, APR_OS_DEFAULT, pool);
5931 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
5933 svn_error_clear(err);
5934 return SVN_NO_ERROR;
5936 SVN_ERR(err);
5938 stream = svn_stream_from_aprfile2(fd, FALSE, pool);
5939 *node_origins = apr_hash_make(pool);
5940 SVN_ERR(svn_hash_read2(*node_origins, stream, SVN_HASH_TERMINATOR, pool));
5941 return svn_stream_close(stream);
5944 svn_error_t *
5945 svn_fs_fs__get_node_origin(const svn_fs_id_t **origin_id,
5946 svn_fs_t *fs,
5947 const char *node_id,
5948 apr_pool_t *pool)
5950 apr_hash_t *node_origins;
5952 *origin_id = NULL;
5953 SVN_ERR(get_node_origins_from_file(fs, &node_origins,
5954 path_node_origin(fs, node_id, pool),
5955 pool));
5956 if (node_origins)
5958 svn_string_t *origin_id_str =
5959 apr_hash_get(node_origins, node_id, APR_HASH_KEY_STRING);
5960 if (origin_id_str)
5961 *origin_id = svn_fs_fs__id_parse(origin_id_str->data,
5962 origin_id_str->len, pool);
5964 return SVN_NO_ERROR;
5968 /* Helper for svn_fs_fs__set_node_origin. Takes a NODE_ID/NODE_REV_ID
5969 pair and adds it to the NODE_ORIGINS_PATH file. */
5970 static svn_error_t *
5971 set_node_origins_for_file(svn_fs_t *fs,
5972 const char *node_origins_path,
5973 const char *node_id,
5974 svn_string_t *node_rev_id,
5975 apr_pool_t *pool)
5977 apr_file_t *fd;
5978 const char *path_tmp;
5979 svn_stream_t *stream;
5980 apr_hash_t *origins_hash;
5981 svn_string_t *old_node_rev_id;
5983 SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_path_join(fs->path,
5984 PATH_NODE_ORIGINS_DIR,
5985 pool),
5986 fs, pool));
5988 /* Read the previously existing origins (if any), and merge our
5989 update with it. */
5990 SVN_ERR(get_node_origins_from_file(fs, &origins_hash,
5991 node_origins_path, pool));
5992 if (! origins_hash)
5993 origins_hash = apr_hash_make(pool);
5995 old_node_rev_id = apr_hash_get(origins_hash, node_id, APR_HASH_KEY_STRING);
5997 if (old_node_rev_id && !svn_string_compare(node_rev_id, old_node_rev_id))
5998 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
5999 _("Node origin for '%s' exists with a different "
6000 "value (%s) than what we were about to store "
6001 "(%s)"),
6002 node_id, old_node_rev_id->data, node_rev_id->data);
6004 apr_hash_set(origins_hash, node_id, APR_HASH_KEY_STRING, node_rev_id);
6006 /* Sure, there's a race condition here. Two processes could be
6007 trying to add different cache elements to the same file at the
6008 same time, and the entries added by the first one to write will
6009 be lost. But this is just a cache of reconstructible data, so
6010 we'll accept this problem in return for not having to deal with
6011 locking overhead. */
6013 /* Create a temporary file, write out our hash, and close the file. */
6014 SVN_ERR(svn_io_open_unique_file2(&fd, &path_tmp, node_origins_path, ".tmp",
6015 svn_io_file_del_none, pool));
6016 stream = svn_stream_from_aprfile2(fd, FALSE, pool);
6017 SVN_ERR(svn_hash_write2(origins_hash, stream, SVN_HASH_TERMINATOR, pool));
6018 SVN_ERR(svn_stream_close(stream));
6020 /* Rename the temp file as the real destination */
6021 SVN_ERR(svn_io_file_rename(path_tmp, node_origins_path, pool));
6023 return SVN_NO_ERROR;
6027 svn_error_t *
6028 svn_fs_fs__set_node_origin(svn_fs_t *fs,
6029 const char *node_id,
6030 const svn_fs_id_t *node_rev_id,
6031 apr_pool_t *pool)
6033 svn_error_t *err;
6034 const char *filename = path_node_origin(fs, node_id, pool);
6036 err = set_node_origins_for_file(fs, filename,
6037 node_id,
6038 svn_fs_fs__id_unparse(node_rev_id, pool),
6039 pool);
6040 if (err && APR_STATUS_IS_EACCES(err->apr_err))
6042 /* It's just a cache; stop trying if I can't write. */
6043 svn_error_clear(err);
6044 err = NULL;
6046 return err;
6050 svn_error_t *
6051 svn_fs_fs__list_transactions(apr_array_header_t **names_p,
6052 svn_fs_t *fs,
6053 apr_pool_t *pool)
6055 const char *txn_dir;
6056 apr_hash_t *dirents;
6057 apr_hash_index_t *hi;
6058 apr_array_header_t *names;
6059 apr_size_t ext_len = strlen(PATH_EXT_TXN);
6061 names = apr_array_make(pool, 1, sizeof(const char *));
6063 /* Get the transactions directory. */
6064 txn_dir = svn_path_join(fs->path, PATH_TXNS_DIR, pool);
6066 /* Now find a listing of this directory. */
6067 SVN_ERR(svn_io_get_dirents2(&dirents, txn_dir, pool));
6069 /* Loop through all the entries and return anything that ends with '.txn'. */
6070 for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi))
6072 const void *key;
6073 const char *name, *id;
6074 apr_ssize_t klen;
6076 apr_hash_this(hi, &key, &klen, NULL);
6077 name = key;
6079 /* The name must end with ".txn" to be considered a transaction. */
6080 if ((apr_size_t) klen <= ext_len
6081 || (strcmp(name + klen - ext_len, PATH_EXT_TXN)) != 0)
6082 continue;
6084 /* Truncate the ".txn" extension and store the ID. */
6085 id = apr_pstrndup(pool, name, strlen(name) - ext_len);
6086 APR_ARRAY_PUSH(names, const char *) = id;
6089 *names_p = names;
6091 return SVN_NO_ERROR;
6094 svn_error_t *
6095 svn_fs_fs__open_txn(svn_fs_txn_t **txn_p,
6096 svn_fs_t *fs,
6097 const char *name,
6098 apr_pool_t *pool)
6100 svn_fs_txn_t *txn;
6101 svn_node_kind_t kind;
6102 transaction_t *local_txn;
6104 /* First check to see if the directory exists. */
6105 SVN_ERR(svn_io_check_path(path_txn_dir(fs, name, pool), &kind, pool));
6107 /* Did we find it? */
6108 if (kind != svn_node_dir)
6109 return svn_error_create(SVN_ERR_FS_NO_SUCH_TRANSACTION, NULL,
6110 _("No such transaction"));
6112 txn = apr_pcalloc(pool, sizeof(*txn));
6114 /* Read in the root node of this transaction. */
6115 txn->id = apr_pstrdup(pool, name);
6116 txn->fs = fs;
6118 SVN_ERR(svn_fs_fs__get_txn(&local_txn, fs, name, pool));
6120 txn->base_rev = svn_fs_fs__id_rev(local_txn->base_id);
6122 txn->vtable = &txn_vtable;
6123 *txn_p = txn;
6125 return SVN_NO_ERROR;
6128 svn_error_t *
6129 svn_fs_fs__txn_proplist(apr_hash_t **table_p,
6130 svn_fs_txn_t *txn,
6131 apr_pool_t *pool)
6133 apr_hash_t *proplist = apr_hash_make(pool);
6134 SVN_ERR(get_txn_proplist(proplist, txn->fs, txn->id, pool));
6135 *table_p = proplist;
6137 return SVN_NO_ERROR;
6140 svn_error_t *
6141 svn_fs_fs__delete_node_revision(svn_fs_t *fs,
6142 const svn_fs_id_t *id,
6143 apr_pool_t *pool)
6145 node_revision_t *noderev;
6147 SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool));
6149 /* Delete any mutable property representation. */
6150 if (noderev->prop_rep && noderev->prop_rep->txn_id)
6151 SVN_ERR(svn_io_remove_file(path_txn_node_props(fs, id, pool), pool));
6153 /* Delete any mutable data representation. */
6154 if (noderev->data_rep && noderev->data_rep->txn_id
6155 && noderev->kind == svn_node_dir)
6156 SVN_ERR(svn_io_remove_file(path_txn_node_children(fs, id, pool), pool));
6158 return svn_io_remove_file(path_txn_node_rev(fs, id, pool), pool);
6163 /*** Revisions ***/
6165 svn_error_t *
6166 svn_fs_fs__revision_prop(svn_string_t **value_p,
6167 svn_fs_t *fs,
6168 svn_revnum_t rev,
6169 const char *propname,
6170 apr_pool_t *pool)
6172 apr_hash_t *table;
6174 SVN_ERR(svn_fs__check_fs(fs, TRUE));
6175 SVN_ERR(svn_fs_fs__revision_proplist(&table, fs, rev, pool));
6177 *value_p = apr_hash_get(table, propname, APR_HASH_KEY_STRING);
6179 return SVN_NO_ERROR;
6183 /* Baton used for change_rev_prop_body below. */
6184 struct change_rev_prop_baton {
6185 svn_fs_t *fs;
6186 svn_revnum_t rev;
6187 const char *name;
6188 const svn_string_t *value;
6191 /* The work-horse for svn_fs_fs__change_rev_prop, called with the FS
6192 write lock. This implements the svn_fs_fs__with_write_lock()
6193 'body' callback type. BATON is a 'struct change_rev_prop_baton *'. */
6195 static svn_error_t *
6196 change_rev_prop_body(void *baton, apr_pool_t *pool)
6198 struct change_rev_prop_baton *cb = baton;
6199 apr_hash_t *table;
6201 SVN_ERR(svn_fs_fs__revision_proplist(&table, cb->fs, cb->rev, pool));
6203 apr_hash_set(table, cb->name, APR_HASH_KEY_STRING, cb->value);
6205 SVN_ERR(svn_fs_fs__set_revision_proplist(cb->fs, cb->rev, table, pool));
6207 return SVN_NO_ERROR;
6210 svn_error_t *
6211 svn_fs_fs__change_rev_prop(svn_fs_t *fs,
6212 svn_revnum_t rev,
6213 const char *name,
6214 const svn_string_t *value,
6215 apr_pool_t *pool)
6217 struct change_rev_prop_baton cb;
6219 SVN_ERR(svn_fs__check_fs(fs, TRUE));
6221 cb.fs = fs;
6222 cb.rev = rev;
6223 cb.name = name;
6224 cb.value = value;
6226 return svn_fs_fs__with_write_lock(fs, change_rev_prop_body, &cb, pool);
6231 /*** Transactions ***/
6233 svn_error_t *
6234 svn_fs_fs__get_txn_ids(const svn_fs_id_t **root_id_p,
6235 const svn_fs_id_t **base_root_id_p,
6236 svn_fs_t *fs,
6237 const char *txn_name,
6238 apr_pool_t *pool)
6240 transaction_t *txn;
6241 SVN_ERR(svn_fs_fs__get_txn(&txn, fs, txn_name, pool));
6242 *root_id_p = txn->root_id;
6243 *base_root_id_p = txn->base_id;
6244 return SVN_NO_ERROR;
6248 /* Generic transaction operations. */
6250 svn_error_t *
6251 svn_fs_fs__txn_prop(svn_string_t **value_p,
6252 svn_fs_txn_t *txn,
6253 const char *propname,
6254 apr_pool_t *pool)
6256 apr_hash_t *table;
6257 svn_fs_t *fs = txn->fs;
6259 SVN_ERR(svn_fs__check_fs(fs, TRUE));
6260 SVN_ERR(svn_fs_fs__txn_proplist(&table, txn, pool));
6262 *value_p = apr_hash_get(table, propname, APR_HASH_KEY_STRING);
6264 return SVN_NO_ERROR;
6267 svn_error_t *
6268 svn_fs_fs__begin_txn(svn_fs_txn_t **txn_p,
6269 svn_fs_t *fs,
6270 svn_revnum_t rev,
6271 apr_uint32_t flags,
6272 apr_pool_t *pool)
6274 svn_string_t date;
6275 svn_prop_t prop;
6276 apr_array_header_t *props = apr_array_make(pool, 3, sizeof(svn_prop_t));
6278 SVN_ERR(svn_fs__check_fs(fs, TRUE));
6280 SVN_ERR(svn_fs_fs__create_txn(txn_p, fs, rev, pool));
6282 /* Put a datestamp on the newly created txn, so we always know
6283 exactly how old it is. (This will help sysadmins identify
6284 long-abandoned txns that may need to be manually removed.) When
6285 a txn is promoted to a revision, this property will be
6286 automatically overwritten with a revision datestamp. */
6287 date.data = svn_time_to_cstring(apr_time_now(), pool);
6288 date.len = strlen(date.data);
6290 prop.name = SVN_PROP_REVISION_DATE;
6291 prop.value = &date;
6292 APR_ARRAY_PUSH(props, svn_prop_t) = prop;
6294 /* Set temporary txn props that represent the requested 'flags'
6295 behaviors. */
6296 if (flags & SVN_FS_TXN_CHECK_OOD)
6298 prop.name = SVN_FS__PROP_TXN_CHECK_OOD;
6299 prop.value = svn_string_create("true", pool);
6300 APR_ARRAY_PUSH(props, svn_prop_t) = prop;
6303 if (flags & SVN_FS_TXN_CHECK_LOCKS)
6305 prop.name = SVN_FS__PROP_TXN_CHECK_LOCKS;
6306 prop.value = svn_string_create("true", pool);
6307 APR_ARRAY_PUSH(props, svn_prop_t) = prop;
6310 return svn_fs_fs__change_txn_props(*txn_p, props, pool);