1 /* fs_fs.c --- filesystem operations specific to fs_fs
2 *
3 * ====================================================================
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 * ====================================================================
21 */
22
23 #include "fs_fs.h"
24
25 #include <apr_uuid.h>
26
27 #include "svn_private_config.h"
28
29 #include "svn_checksum.h"
30 #include "svn_hash.h"
31 #include "svn_props.h"
32 #include "svn_time.h"
33 #include "svn_dirent_uri.h"
34 #include "svn_sorts.h"
35 #include "svn_version.h"
36
37 #include "cached_data.h"
38 #include "id.h"
39 #include "index.h"
40 #include "low_level.h"
41 #include "rep-cache.h"
42 #include "revprops.h"
43 #include "transaction.h"
44 #include "tree.h"
45 #include "util.h"
46
47 #include "private/svn_fs_util.h"
48 #include "private/svn_io_private.h"
49 #include "private/svn_string_private.h"
50 #include "private/svn_subr_private.h"
51 #include "../libsvn_fs/fs-loader.h"
52
53 /* The default maximum number of files per directory to store in the
54 rev and revprops directory. The number below is somewhat arbitrary,
55 and can be overridden by defining the macro while compiling; the
56 figure of 1000 is reasonable for VFAT filesystems, which are by far
57 the worst performers in this area. */
58 #ifndef SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR
59 #define SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR 1000
60 #endif
61
62 /* Begin deltification after a node history exceeded this this limit.
63 Useful values are 4 to 64 with 16 being a good compromise between
64 computational overhead and repository size savings.
65 Should be a power of 2.
66 Values < 2 will result in standard skip-delta behavior. */
67 #define SVN_FS_FS_MAX_LINEAR_DELTIFICATION 16
68
69 /* Finding a deltification base takes operations proportional to the
70 number of changes being skipped. To prevent exploding runtime
71 during commits, limit the deltification range to this value.
72 Should be a power of 2 minus one.
73 Values < 1 disable deltification. */
74 #define SVN_FS_FS_MAX_DELTIFICATION_WALK 1023
75
76 /* Notes:
77
78 To avoid opening and closing the rev-files all the time, it would
79 probably be advantageous to keep each rev-file open for the
80 lifetime of the transaction object. I'll leave that as a later
81 optimization for now.
82
83 I didn't keep track of pool lifetimes at all in this code. There
84 are likely some errors because of that.
85
86 */
87
88 /* Declarations. */
89
90 static svn_error_t *
91 get_youngest(svn_revnum_t *youngest_p, svn_fs_t *fs, apr_pool_t *pool);
92
93 /* Pathname helper functions */
94
95 static const char *
path_format(svn_fs_t * fs,apr_pool_t * pool)96 path_format(svn_fs_t *fs, apr_pool_t *pool)
97 {
98 return svn_dirent_join(fs->path, PATH_FORMAT, pool);
99 }
100
101 static APR_INLINE const char *
path_uuid(svn_fs_t * fs,apr_pool_t * pool)102 path_uuid(svn_fs_t *fs, apr_pool_t *pool)
103 {
104 return svn_dirent_join(fs->path, PATH_UUID, pool);
105 }
106
107 const char *
svn_fs_fs__path_current(svn_fs_t * fs,apr_pool_t * pool)108 svn_fs_fs__path_current(svn_fs_t *fs, apr_pool_t *pool)
109 {
110 return svn_dirent_join(fs->path, PATH_CURRENT, pool);
111 }
112
113
114
115 /* Get a lock on empty file LOCK_FILENAME, creating it in POOL. */
116 static svn_error_t *
get_lock_on_filesystem(const char * lock_filename,apr_pool_t * pool)117 get_lock_on_filesystem(const char *lock_filename,
118 apr_pool_t *pool)
119 {
120 return svn_error_trace(svn_io__file_lock_autocreate(lock_filename, pool));
121 }
122
123 /* Reset the HAS_WRITE_LOCK member in the FFD given as BATON_VOID.
124 When registered with the pool holding the lock on the lock file,
125 this makes sure the flag gets reset just before we release the lock. */
126 static apr_status_t
reset_lock_flag(void * baton_void)127 reset_lock_flag(void *baton_void)
128 {
129 fs_fs_data_t *ffd = baton_void;
130 ffd->has_write_lock = FALSE;
131 return APR_SUCCESS;
132 }
133
134 /* Structure defining a file system lock to be acquired and the function
135 to be executed while the lock is held.
136
137 Instances of this structure may be nested to allow for multiple locks to
138 be taken out before executing the user-provided body. In that case, BODY
139 and BATON of the outer instances will be with_lock and a with_lock_baton_t
140 instance (transparently, no special treatment is required.). It is
141 illegal to attempt to acquire the same lock twice within the same lock
142 chain or via nesting calls using separate lock chains.
143
144 All instances along the chain share the same LOCK_POOL such that only one
145 pool needs to be created and cleared for all locks. We also allocate as
146 much data from that lock pool as possible to minimize memory usage in
147 caller pools. */
148 typedef struct with_lock_baton_t
149 {
150 /* The filesystem we operate on. Same for all instances along the chain. */
151 svn_fs_t *fs;
152
153 /* Mutex to complement the lock file in an APR threaded process.
154 No-op object for non-threaded processes but never NULL. */
155 svn_mutex__t *mutex;
156
157 /* Path to the file to lock. */
158 const char *lock_path;
159
160 /* If true, set FS->HAS_WRITE_LOCK after we acquired the lock. */
161 svn_boolean_t is_global_lock;
162
163 /* Function body to execute after we acquired the lock.
164 This may be user-provided or a nested call to with_lock(). */
165 svn_error_t *(*body)(void *baton,
166 apr_pool_t *pool);
167
168 /* Baton to pass to BODY; possibly NULL.
169 This may be user-provided or a nested lock baton instance. */
170 void *baton;
171
172 /* Pool for all allocations along the lock chain and BODY. Will hold the
173 file locks and gets destroyed after the outermost BODY returned,
174 releasing all file locks.
175 Same for all instances along the chain. */
176 apr_pool_t *lock_pool;
177
178 /* TRUE, iff BODY is the user-provided body. */
179 svn_boolean_t is_inner_most_lock;
180
181 /* TRUE, iff this is not a nested lock.
182 Then responsible for destroying LOCK_POOL. */
183 svn_boolean_t is_outer_most_lock;
184 } with_lock_baton_t;
185
186 /* Obtain a write lock on the file BATON->LOCK_PATH and call BATON->BODY
187 with BATON->BATON. If this is the outermost lock call, release all file
188 locks after the body returned. If BATON->IS_GLOBAL_LOCK is set, set the
189 HAS_WRITE_LOCK flag while we keep the write lock. */
190 static svn_error_t *
with_some_lock_file(with_lock_baton_t * baton)191 with_some_lock_file(with_lock_baton_t *baton)
192 {
193 apr_pool_t *pool = baton->lock_pool;
194 svn_error_t *err = get_lock_on_filesystem(baton->lock_path, pool);
195
196 if (!err)
197 {
198 svn_fs_t *fs = baton->fs;
199 fs_fs_data_t *ffd = fs->fsap_data;
200
201 if (baton->is_global_lock)
202 {
203 /* set the "got the lock" flag and register reset function */
204 apr_pool_cleanup_register(pool,
205 ffd,
206 reset_lock_flag,
207 apr_pool_cleanup_null);
208 ffd->has_write_lock = TRUE;
209 }
210
211 /* nobody else will modify the repo state
212 => read HEAD & pack info once */
213 if (baton->is_inner_most_lock)
214 {
215 if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
216 err = svn_fs_fs__update_min_unpacked_rev(fs, pool);
217 if (!err)
218 err = get_youngest(&ffd->youngest_rev_cache, fs, pool);
219 }
220
221 if (!err)
222 err = baton->body(baton->baton, pool);
223 }
224
225 if (baton->is_outer_most_lock)
226 svn_pool_destroy(pool);
227
228 return svn_error_trace(err);
229 }
230
231 /* Wraps with_some_lock_file, protecting it with BATON->MUTEX.
232
233 POOL is unused here and only provided for signature compatibility with
234 WITH_LOCK_BATON_T.BODY. */
235 static svn_error_t *
with_lock(void * baton,apr_pool_t * pool)236 with_lock(void *baton,
237 apr_pool_t *pool)
238 {
239 with_lock_baton_t *lock_baton = baton;
240 SVN_MUTEX__WITH_LOCK(lock_baton->mutex, with_some_lock_file(lock_baton));
241
242 return SVN_NO_ERROR;
243 }
244
245 /* Enum identifying a filesystem lock. */
246 typedef enum lock_id_t
247 {
248 write_lock,
249 txn_lock,
250 pack_lock
251 } lock_id_t;
252
253 /* Initialize BATON->MUTEX, BATON->LOCK_PATH and BATON->IS_GLOBAL_LOCK
254 according to the LOCK_ID. All other members of BATON must already be
255 valid. */
256 static void
init_lock_baton(with_lock_baton_t * baton,lock_id_t lock_id)257 init_lock_baton(with_lock_baton_t *baton,
258 lock_id_t lock_id)
259 {
260 fs_fs_data_t *ffd = baton->fs->fsap_data;
261 fs_fs_shared_data_t *ffsd = ffd->shared;
262
263 switch (lock_id)
264 {
265 case write_lock:
266 baton->mutex = ffsd->fs_write_lock;
267 baton->lock_path = svn_fs_fs__path_lock(baton->fs, baton->lock_pool);
268 baton->is_global_lock = TRUE;
269 break;
270
271 case txn_lock:
272 baton->mutex = ffsd->txn_current_lock;
273 baton->lock_path = svn_fs_fs__path_txn_current_lock(baton->fs,
274 baton->lock_pool);
275 baton->is_global_lock = FALSE;
276 break;
277
278 case pack_lock:
279 baton->mutex = ffsd->fs_pack_lock;
280 baton->lock_path = svn_fs_fs__path_pack_lock(baton->fs,
281 baton->lock_pool);
282 baton->is_global_lock = FALSE;
283 break;
284 }
285 }
286
287 /* Return the baton for the innermost lock of a (potential) lock chain.
288 The baton shall take out LOCK_ID from FS and execute BODY with BATON
289 while the lock is being held. Allocate the result in a sub-pool of POOL.
290 */
291 static with_lock_baton_t *
create_lock_baton(svn_fs_t * fs,lock_id_t lock_id,svn_error_t * (* body)(void * baton,apr_pool_t * pool),void * baton,apr_pool_t * pool)292 create_lock_baton(svn_fs_t *fs,
293 lock_id_t lock_id,
294 svn_error_t *(*body)(void *baton,
295 apr_pool_t *pool),
296 void *baton,
297 apr_pool_t *pool)
298 {
299 /* Allocate everything along the lock chain into a single sub-pool.
300 This minimizes memory usage and cleanup overhead. */
301 apr_pool_t *lock_pool = svn_pool_create(pool);
302 with_lock_baton_t *result = apr_pcalloc(lock_pool, sizeof(*result));
303
304 /* Store parameters. */
305 result->fs = fs;
306 result->body = body;
307 result->baton = baton;
308
309 /* File locks etc. will use this pool as well for easy cleanup. */
310 result->lock_pool = lock_pool;
311
312 /* Right now, we are the first, (only, ) and last struct in the chain. */
313 result->is_inner_most_lock = TRUE;
314 result->is_outer_most_lock = TRUE;
315
316 /* Select mutex and lock file path depending on LOCK_ID.
317 Also, initialize dependent members (IS_GLOBAL_LOCK only, ATM). */
318 init_lock_baton(result, lock_id);
319
320 return result;
321 }
322
323 /* Return a baton that wraps NESTED and requests LOCK_ID as additional lock.
324 *
325 * That means, when you create a lock chain, start with the last / innermost
326 * lock to take out and add the first / outermost lock last.
327 */
328 static with_lock_baton_t *
chain_lock_baton(lock_id_t lock_id,with_lock_baton_t * nested)329 chain_lock_baton(lock_id_t lock_id,
330 with_lock_baton_t *nested)
331 {
332 /* Use the same pool for batons along the lock chain. */
333 apr_pool_t *lock_pool = nested->lock_pool;
334 with_lock_baton_t *result = apr_pcalloc(lock_pool, sizeof(*result));
335
336 /* All locks along the chain operate on the same FS. */
337 result->fs = nested->fs;
338
339 /* Execution of this baton means acquiring the nested lock and its
340 execution. */
341 result->body = with_lock;
342 result->baton = nested;
343
344 /* Shared among all locks along the chain. */
345 result->lock_pool = lock_pool;
346
347 /* We are the new outermost lock but surely not the innermost lock. */
348 result->is_inner_most_lock = FALSE;
349 result->is_outer_most_lock = TRUE;
350 nested->is_outer_most_lock = FALSE;
351
352 /* Select mutex and lock file path depending on LOCK_ID.
353 Also, initialize dependent members (IS_GLOBAL_LOCK only, ATM). */
354 init_lock_baton(result, lock_id);
355
356 return result;
357 }
358
359 svn_error_t *
svn_fs_fs__with_write_lock(svn_fs_t * fs,svn_error_t * (* body)(void * baton,apr_pool_t * pool),void * baton,apr_pool_t * pool)360 svn_fs_fs__with_write_lock(svn_fs_t *fs,
361 svn_error_t *(*body)(void *baton,
362 apr_pool_t *pool),
363 void *baton,
364 apr_pool_t *pool)
365 {
366 return svn_error_trace(
367 with_lock(create_lock_baton(fs, write_lock, body, baton, pool),
368 pool));
369 }
370
371 svn_error_t *
svn_fs_fs__with_pack_lock(svn_fs_t * fs,svn_error_t * (* body)(void * baton,apr_pool_t * pool),void * baton,apr_pool_t * pool)372 svn_fs_fs__with_pack_lock(svn_fs_t *fs,
373 svn_error_t *(*body)(void *baton,
374 apr_pool_t *pool),
375 void *baton,
376 apr_pool_t *pool)
377 {
378 return svn_error_trace(
379 with_lock(create_lock_baton(fs, pack_lock, body, baton, pool),
380 pool));
381 }
382
383 svn_error_t *
svn_fs_fs__with_txn_current_lock(svn_fs_t * fs,svn_error_t * (* body)(void * baton,apr_pool_t * pool),void * baton,apr_pool_t * pool)384 svn_fs_fs__with_txn_current_lock(svn_fs_t *fs,
385 svn_error_t *(*body)(void *baton,
386 apr_pool_t *pool),
387 void *baton,
388 apr_pool_t *pool)
389 {
390 return svn_error_trace(
391 with_lock(create_lock_baton(fs, txn_lock, body, baton, pool),
392 pool));
393 }
394
395 svn_error_t *
svn_fs_fs__with_all_locks(svn_fs_t * fs,svn_error_t * (* body)(void * baton,apr_pool_t * pool),void * baton,apr_pool_t * pool)396 svn_fs_fs__with_all_locks(svn_fs_t *fs,
397 svn_error_t *(*body)(void *baton,
398 apr_pool_t *pool),
399 void *baton,
400 apr_pool_t *pool)
401 {
402 fs_fs_data_t *ffd = fs->fsap_data;
403
404 /* Be sure to use the correct lock ordering as documented in
405 fs_fs_shared_data_t. The lock chain is being created in
406 innermost (last to acquire) -> outermost (first to acquire) order. */
407 with_lock_baton_t *lock_baton
408 = create_lock_baton(fs, write_lock, body, baton, pool);
409
410 if (ffd->format >= SVN_FS_FS__MIN_PACK_LOCK_FORMAT)
411 lock_baton = chain_lock_baton(pack_lock, lock_baton);
412
413 if (ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
414 lock_baton = chain_lock_baton(txn_lock, lock_baton);
415
416 return svn_error_trace(with_lock(lock_baton, pool));
417 }
418
419
420
421
422
423 /* Check that BUF, a nul-terminated buffer of text from format file PATH,
424 contains only digits at OFFSET and beyond, raising an error if not.
425
426 Uses POOL for temporary allocation. */
427 static svn_error_t *
check_format_file_buffer_numeric(const char * buf,apr_off_t offset,const char * path,apr_pool_t * pool)428 check_format_file_buffer_numeric(const char *buf, apr_off_t offset,
429 const char *path, apr_pool_t *pool)
430 {
431 return svn_fs_fs__check_file_buffer_numeric(buf, offset, path, "Format",
432 pool);
433 }
434
435 /* Return the error SVN_ERR_FS_UNSUPPORTED_FORMAT if FS's format
436 number is not the same as a format number supported by this
437 Subversion. */
438 static svn_error_t *
check_format(int format)439 check_format(int format)
440 {
441 /* Blacklist. These formats may be either younger or older than
442 SVN_FS_FS__FORMAT_NUMBER, but we don't support them. */
443 if (format == SVN_FS_FS__PACKED_REVPROP_SQLITE_DEV_FORMAT)
444 return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
445 _("Found format '%d', only created by "
446 "unreleased dev builds; see "
447 "http://subversion.apache.org"
448 "/docs/release-notes/1.7#revprop-packing"),
449 format);
450
451 /* We support all formats from 1-current simultaneously */
452 if (1 <= format && format <= SVN_FS_FS__FORMAT_NUMBER)
453 return SVN_NO_ERROR;
454
455 return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
456 _("Expected FS format between '1' and '%d'; found format '%d'"),
457 SVN_FS_FS__FORMAT_NUMBER, format);
458 }
459
460 /* Read the format number and maximum number of files per directory
461 from PATH and return them in *PFORMAT, *MAX_FILES_PER_DIR and
462 USE_LOG_ADDRESSIONG respectively.
463
464 *MAX_FILES_PER_DIR is obtained from the 'layout' format option, and
465 will be set to zero if a linear scheme should be used.
466 *USE_LOG_ADDRESSIONG is obtained from the 'addressing' format option,
467 and will be set to FALSE for physical addressing.
468
469 Use POOL for temporary allocation. */
470 static svn_error_t *
read_format(int * pformat,int * max_files_per_dir,svn_boolean_t * use_log_addressing,const char * path,apr_pool_t * pool)471 read_format(int *pformat,
472 int *max_files_per_dir,
473 svn_boolean_t *use_log_addressing,
474 const char *path,
475 apr_pool_t *pool)
476 {
477 svn_error_t *err;
478 svn_stream_t *stream;
479 svn_stringbuf_t *content;
480 svn_stringbuf_t *buf;
481 svn_boolean_t eos = FALSE;
482
483 err = svn_stringbuf_from_file2(&content, path, pool);
484 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
485 {
486 /* Treat an absent format file as format 1. Do not try to
487 create the format file on the fly, because the repository
488 might be read-only for us, or this might be a read-only
489 operation, and the spirit of FSFS is to make no changes
490 whatseover in read-only operations. See thread starting at
491 http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=97600
492 for more. */
493 svn_error_clear(err);
494 *pformat = 1;
495 *max_files_per_dir = 0;
496 *use_log_addressing = FALSE;
497
498 return SVN_NO_ERROR;
499 }
500 SVN_ERR(err);
501
502 stream = svn_stream_from_stringbuf(content, pool);
503 SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eos, pool));
504 if (buf->len == 0 && eos)
505 {
506 /* Return a more useful error message. */
507 return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
508 _("Can't read first line of format file '%s'"),
509 svn_dirent_local_style(path, pool));
510 }
511
512 /* Check that the first line contains only digits. */
513 SVN_ERR(check_format_file_buffer_numeric(buf->data, 0, path, pool));
514 SVN_ERR(svn_cstring_atoi(pformat, buf->data));
515
516 /* Check that we support this format at all */
517 SVN_ERR(check_format(*pformat));
518
519 /* Set the default values for anything that can be set via an option. */
520 *max_files_per_dir = 0;
521 *use_log_addressing = FALSE;
522
523 /* Read any options. */
524 while (!eos)
525 {
526 SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eos, pool));
527 if (buf->len == 0)
528 break;
529
530 if (*pformat >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT &&
531 strncmp(buf->data, "layout ", 7) == 0)
532 {
533 if (strcmp(buf->data + 7, "linear") == 0)
534 {
535 *max_files_per_dir = 0;
536 continue;
537 }
538
539 if (strncmp(buf->data + 7, "sharded ", 8) == 0)
540 {
541 /* Check that the argument is numeric. */
542 SVN_ERR(check_format_file_buffer_numeric(buf->data, 15, path, pool));
543 SVN_ERR(svn_cstring_atoi(max_files_per_dir, buf->data + 15));
544 continue;
545 }
546 }
547
548 if (*pformat >= SVN_FS_FS__MIN_LOG_ADDRESSING_FORMAT &&
549 strncmp(buf->data, "addressing ", 11) == 0)
550 {
551 if (strcmp(buf->data + 11, "physical") == 0)
552 {
553 *use_log_addressing = FALSE;
554 continue;
555 }
556
557 if (strcmp(buf->data + 11, "logical") == 0)
558 {
559 *use_log_addressing = TRUE;
560 continue;
561 }
562 }
563
564 return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
565 _("'%s' contains invalid filesystem format option '%s'"),
566 svn_dirent_local_style(path, pool), buf->data);
567 }
568
569 /* Non-sharded repositories never use logical addressing.
570 * If the format file is inconsistent in that respect, something
571 * probably went wrong.
572 */
573 if (*use_log_addressing && !*max_files_per_dir)
574 return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
575 _("'%s' specifies logical addressing for a non-sharded repository"),
576 svn_dirent_local_style(path, pool));
577
578 return SVN_NO_ERROR;
579 }
580
581 /* Write the format number, maximum number of files per directory and
582 the addressing scheme to a new format file in PATH, possibly expecting
583 to overwrite a previously existing file.
584
585 Use POOL for temporary allocation. */
586 svn_error_t *
svn_fs_fs__write_format(svn_fs_t * fs,svn_boolean_t overwrite,apr_pool_t * pool)587 svn_fs_fs__write_format(svn_fs_t *fs,
588 svn_boolean_t overwrite,
589 apr_pool_t *pool)
590 {
591 svn_stringbuf_t *sb;
592 fs_fs_data_t *ffd = fs->fsap_data;
593 const char *path = path_format(fs, pool);
594
595 SVN_ERR_ASSERT(1 <= ffd->format
596 && ffd->format <= SVN_FS_FS__FORMAT_NUMBER);
597
598 sb = svn_stringbuf_createf(pool, "%d\n", ffd->format);
599
600 if (ffd->format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
601 {
602 if (ffd->max_files_per_dir)
603 svn_stringbuf_appendcstr(sb, apr_psprintf(pool, "layout sharded %d\n",
604 ffd->max_files_per_dir));
605 else
606 svn_stringbuf_appendcstr(sb, "layout linear\n");
607 }
608
609 if (ffd->format >= SVN_FS_FS__MIN_LOG_ADDRESSING_FORMAT)
610 {
611 if (ffd->use_log_addressing)
612 svn_stringbuf_appendcstr(sb, "addressing logical\n");
613 else
614 svn_stringbuf_appendcstr(sb, "addressing physical\n");
615 }
616
617 /* svn_io_write_version_file() does a load of magic to allow it to
618 replace version files that already exist. We only need to do
619 that when we're allowed to overwrite an existing file. */
620 if (! overwrite)
621 {
622 /* Create the file */
623 SVN_ERR(svn_io_file_create(path, sb->data, pool));
624 }
625 else
626 {
627 SVN_ERR(svn_io_write_atomic2(path, sb->data, sb->len,
628 NULL /* copy_perms_path */,
629 ffd->flush_to_disk, pool));
630 }
631
632 /* And set the perms to make it read only */
633 return svn_io_set_file_read_only(path, FALSE, pool);
634 }
635
636 svn_boolean_t
svn_fs_fs__fs_supports_mergeinfo(svn_fs_t * fs)637 svn_fs_fs__fs_supports_mergeinfo(svn_fs_t *fs)
638 {
639 fs_fs_data_t *ffd = fs->fsap_data;
640 return ffd->format >= SVN_FS_FS__MIN_MERGEINFO_FORMAT;
641 }
642
643 /* Check that BLOCK_SIZE is a valid block / page size, i.e. it is within
644 * the range of what the current system may address in RAM and it is a
645 * power of 2. Assume that the element size within the block is ITEM_SIZE.
646 * Use SCRATCH_POOL for temporary allocations.
647 */
648 static svn_error_t *
verify_block_size(apr_int64_t block_size,apr_size_t item_size,const char * name,apr_pool_t * scratch_pool)649 verify_block_size(apr_int64_t block_size,
650 apr_size_t item_size,
651 const char *name,
652 apr_pool_t *scratch_pool
653 )
654 {
655 /* Limit range. */
656 if (block_size <= 0)
657 return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
658 _("%s is too small for fsfs.conf setting '%s'."),
659 apr_psprintf(scratch_pool,
660 "%" APR_INT64_T_FMT,
661 block_size),
662 name);
663
664 if (block_size > SVN_MAX_OBJECT_SIZE / item_size)
665 return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
666 _("%s is too large for fsfs.conf setting '%s'."),
667 apr_psprintf(scratch_pool,
668 "%" APR_INT64_T_FMT,
669 block_size),
670 name);
671
672 /* Ensure it is a power of two.
673 * For positive X, X & (X-1) will reset the lowest bit set.
674 * If the result is 0, at most one bit has been set. */
675 if (0 != (block_size & (block_size - 1)))
676 return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
677 _("%s is invalid for fsfs.conf setting '%s' "
678 "because it is not a power of 2."),
679 apr_psprintf(scratch_pool,
680 "%" APR_INT64_T_FMT,
681 block_size),
682 name);
683
684 return SVN_NO_ERROR;
685 }
686
687 static svn_error_t *
parse_compression_option(compression_type_t * compression_type_p,int * compression_level_p,const char * value)688 parse_compression_option(compression_type_t *compression_type_p,
689 int *compression_level_p,
690 const char *value)
691 {
692 compression_type_t type;
693 int level;
694 svn_boolean_t is_valid = TRUE;
695
696 /* compression = none | lz4 | zlib | zlib-1 ... zlib-9 */
697 if (strcmp(value, "none") == 0)
698 {
699 type = compression_type_none;
700 level = SVN_DELTA_COMPRESSION_LEVEL_NONE;
701 }
702 else if (strcmp(value, "lz4") == 0)
703 {
704 type = compression_type_lz4;
705 level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT;
706 }
707 else if (strncmp(value, "zlib", 4) == 0)
708 {
709 const char *p = value + 4;
710
711 type = compression_type_zlib;
712 if (*p == 0)
713 {
714 level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT;
715 }
716 else if (*p == '-')
717 {
718 p++;
719 SVN_ERR(svn_cstring_atoi(&level, p));
720 if (level < 1 || level > 9)
721 is_valid = FALSE;
722 }
723 else
724 is_valid = FALSE;
725 }
726 else
727 {
728 is_valid = FALSE;
729 }
730
731 if (!is_valid)
732 return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
733 _("Invalid 'compression' value '%s' in the config"),
734 value);
735
736 *compression_type_p = type;
737 *compression_level_p = level;
738 return SVN_NO_ERROR;
739 }
740
741 /* Read the configuration information of the file system at FS_PATH
742 * and set the respective values in FFD. Use pools as usual.
743 */
744 static svn_error_t *
read_config(fs_fs_data_t * ffd,const char * fs_path,apr_pool_t * result_pool,apr_pool_t * scratch_pool)745 read_config(fs_fs_data_t *ffd,
746 const char *fs_path,
747 apr_pool_t *result_pool,
748 apr_pool_t *scratch_pool)
749 {
750 svn_config_t *config;
751
752 SVN_ERR(svn_config_read3(&config,
753 svn_dirent_join(fs_path, PATH_CONFIG, scratch_pool),
754 FALSE, FALSE, FALSE, scratch_pool));
755
756 /* Initialize ffd->rep_sharing_allowed. */
757 if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
758 SVN_ERR(svn_config_get_bool(config, &ffd->rep_sharing_allowed,
759 CONFIG_SECTION_REP_SHARING,
760 CONFIG_OPTION_ENABLE_REP_SHARING, TRUE));
761 else
762 ffd->rep_sharing_allowed = FALSE;
763
764 /* Initialize deltification settings in ffd. */
765 if (ffd->format >= SVN_FS_FS__MIN_DELTIFICATION_FORMAT)
766 {
767 SVN_ERR(svn_config_get_bool(config, &ffd->deltify_directories,
768 CONFIG_SECTION_DELTIFICATION,
769 CONFIG_OPTION_ENABLE_DIR_DELTIFICATION,
770 TRUE));
771 SVN_ERR(svn_config_get_bool(config, &ffd->deltify_properties,
772 CONFIG_SECTION_DELTIFICATION,
773 CONFIG_OPTION_ENABLE_PROPS_DELTIFICATION,
774 TRUE));
775 SVN_ERR(svn_config_get_int64(config, &ffd->max_deltification_walk,
776 CONFIG_SECTION_DELTIFICATION,
777 CONFIG_OPTION_MAX_DELTIFICATION_WALK,
778 SVN_FS_FS_MAX_DELTIFICATION_WALK));
779 SVN_ERR(svn_config_get_int64(config, &ffd->max_linear_deltification,
780 CONFIG_SECTION_DELTIFICATION,
781 CONFIG_OPTION_MAX_LINEAR_DELTIFICATION,
782 SVN_FS_FS_MAX_LINEAR_DELTIFICATION));
783 }
784 else
785 {
786 ffd->deltify_directories = FALSE;
787 ffd->deltify_properties = FALSE;
788 ffd->max_deltification_walk = SVN_FS_FS_MAX_DELTIFICATION_WALK;
789 ffd->max_linear_deltification = SVN_FS_FS_MAX_LINEAR_DELTIFICATION;
790 }
791
792 /* Initialize revprop packing settings in ffd. */
793 if (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
794 {
795 SVN_ERR(svn_config_get_bool(config, &ffd->compress_packed_revprops,
796 CONFIG_SECTION_PACKED_REVPROPS,
797 CONFIG_OPTION_COMPRESS_PACKED_REVPROPS,
798 FALSE));
799 SVN_ERR(svn_config_get_int64(config, &ffd->revprop_pack_size,
800 CONFIG_SECTION_PACKED_REVPROPS,
801 CONFIG_OPTION_REVPROP_PACK_SIZE,
802 ffd->compress_packed_revprops
803 ? 0x40
804 : 0x10));
805
806 ffd->revprop_pack_size *= 1024;
807 }
808 else
809 {
810 ffd->revprop_pack_size = 0x10000;
811 ffd->compress_packed_revprops = FALSE;
812 }
813
814 if (ffd->format >= SVN_FS_FS__MIN_LOG_ADDRESSING_FORMAT)
815 {
816 SVN_ERR(svn_config_get_int64(config, &ffd->block_size,
817 CONFIG_SECTION_IO,
818 CONFIG_OPTION_BLOCK_SIZE,
819 64));
820 SVN_ERR(svn_config_get_int64(config, &ffd->l2p_page_size,
821 CONFIG_SECTION_IO,
822 CONFIG_OPTION_L2P_PAGE_SIZE,
823 0x2000));
824 SVN_ERR(svn_config_get_int64(config, &ffd->p2l_page_size,
825 CONFIG_SECTION_IO,
826 CONFIG_OPTION_P2L_PAGE_SIZE,
827 0x400));
828
829 /* Don't accept unreasonable or illegal values.
830 * Block size and P2L page size are in kbytes;
831 * L2P blocks are arrays of apr_off_t. */
832 SVN_ERR(verify_block_size(ffd->block_size, 0x400,
833 CONFIG_OPTION_BLOCK_SIZE, scratch_pool));
834 SVN_ERR(verify_block_size(ffd->p2l_page_size, 0x400,
835 CONFIG_OPTION_P2L_PAGE_SIZE, scratch_pool));
836 SVN_ERR(verify_block_size(ffd->l2p_page_size, sizeof(apr_off_t),
837 CONFIG_OPTION_L2P_PAGE_SIZE, scratch_pool));
838
839 /* convert kBytes to bytes */
840 ffd->block_size *= 0x400;
841 ffd->p2l_page_size *= 0x400;
842 /* L2P pages are in entries - not in (k)Bytes */
843 }
844 else
845 {
846 /* should be irrelevant but we initialize them anyway */
847 ffd->block_size = 0x1000; /* Matches default APR file buffer size. */
848 ffd->l2p_page_size = 0x2000; /* Matches above default. */
849 ffd->p2l_page_size = 0x100000; /* Matches above default in bytes. */
850 }
851
852 if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
853 {
854 SVN_ERR(svn_config_get_bool(config, &ffd->pack_after_commit,
855 CONFIG_SECTION_DEBUG,
856 CONFIG_OPTION_PACK_AFTER_COMMIT,
857 FALSE));
858 }
859 else
860 {
861 ffd->pack_after_commit = FALSE;
862 }
863
864 /* Initialize compression settings in ffd. */
865 if (ffd->format >= SVN_FS_FS__MIN_DELTIFICATION_FORMAT)
866 {
867 const char *compression_val;
868 const char *compression_level_val;
869
870 svn_config_get(config, &compression_val,
871 CONFIG_SECTION_DELTIFICATION,
872 CONFIG_OPTION_COMPRESSION, NULL);
873 svn_config_get(config, &compression_level_val,
874 CONFIG_SECTION_DELTIFICATION,
875 CONFIG_OPTION_COMPRESSION_LEVEL, NULL);
876 if (compression_val && compression_level_val)
877 {
878 return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL,
879 _("The 'compression' and 'compression-level' "
880 "config options are mutually exclusive"));
881 }
882 else if (compression_val)
883 {
884 SVN_ERR(parse_compression_option(&ffd->delta_compression_type,
885 &ffd->delta_compression_level,
886 compression_val));
887 if (ffd->delta_compression_type == compression_type_lz4 &&
888 ffd->format < SVN_FS_FS__MIN_SVNDIFF2_FORMAT)
889 {
890 return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL,
891 _("Compression type 'lz4' requires "
892 "filesystem format 8 or higher"));
893 }
894 }
895 else if (compression_level_val)
896 {
897 /* Handle the deprecated 'compression-level' option. */
898 ffd->delta_compression_type = compression_type_zlib;
899 SVN_ERR(svn_cstring_atoi(&ffd->delta_compression_level,
900 compression_level_val));
901 ffd->delta_compression_level =
902 MIN(MAX(SVN_DELTA_COMPRESSION_LEVEL_NONE,
903 ffd->delta_compression_level),
904 SVN_DELTA_COMPRESSION_LEVEL_MAX);
905 }
906 else
907 {
908 /* Nothing specified explicitly, use the default settings:
909 * LZ4 compression for formats supporting it and zlib otherwise. */
910 if (ffd->format >= SVN_FS_FS__MIN_SVNDIFF2_FORMAT)
911 ffd->delta_compression_type = compression_type_lz4;
912 else
913 ffd->delta_compression_type = compression_type_zlib;
914
915 ffd->delta_compression_level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT;
916 }
917 }
918 else if (ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT)
919 {
920 ffd->delta_compression_type = compression_type_zlib;
921 ffd->delta_compression_level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT;
922 }
923 else
924 {
925 ffd->delta_compression_type = compression_type_none;
926 ffd->delta_compression_level = SVN_DELTA_COMPRESSION_LEVEL_NONE;
927 }
928
929 #ifdef SVN_DEBUG
930 SVN_ERR(svn_config_get_bool(config, &ffd->verify_before_commit,
931 CONFIG_SECTION_DEBUG,
932 CONFIG_OPTION_VERIFY_BEFORE_COMMIT,
933 TRUE));
934 #else
935 SVN_ERR(svn_config_get_bool(config, &ffd->verify_before_commit,
936 CONFIG_SECTION_DEBUG,
937 CONFIG_OPTION_VERIFY_BEFORE_COMMIT,
938 FALSE));
939 #endif
940
941 /* memcached configuration */
942 SVN_ERR(svn_cache__make_memcache_from_config(&ffd->memcache, config,
943 result_pool, scratch_pool));
944
945 SVN_ERR(svn_config_get_bool(config, &ffd->fail_stop,
946 CONFIG_SECTION_CACHES, CONFIG_OPTION_FAIL_STOP,
947 FALSE));
948
949 return SVN_NO_ERROR;
950 }
951
952 static svn_error_t *
write_config(svn_fs_t * fs,apr_pool_t * pool)953 write_config(svn_fs_t *fs,
954 apr_pool_t *pool)
955 {
956 #define NL APR_EOL_STR
957 static const char * const fsfs_conf_contents =
958 "### This file controls the configuration of the FSFS filesystem." NL
959 "" NL
960 "[" SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS "]" NL
961 "### These options name memcached servers used to cache internal FSFS" NL
962 "### data. See http://www.danga.com/memcached/ for more information on" NL
963 "### memcached. To use memcached with FSFS, run one or more memcached" NL
964 "### servers, and specify each of them as an option like so:" NL
965 "# first-server = 127.0.0.1:11211" NL
966 "# remote-memcached = mymemcached.corp.example.com:11212" NL
967 "### The option name is ignored; the value is of the form HOST:PORT." NL
968 "### memcached servers can be shared between multiple repositories;" NL
969 "### however, if you do this, you *must* ensure that repositories have" NL
970 "### distinct UUIDs and paths, or else cached data from one repository" NL
971 "### might be used by another accidentally. Note also that memcached has" NL
972 "### no authentication for reads or writes, so you must ensure that your" NL
973 "### memcached servers are only accessible by trusted users." NL
974 "" NL
975 "[" CONFIG_SECTION_CACHES "]" NL
976 "### When a cache-related error occurs, normally Subversion ignores it" NL
977 "### and continues, logging an error if the server is appropriately" NL
978 "### configured (and ignoring it with file:// access). To make" NL
979 "### Subversion never ignore cache errors, uncomment this line." NL
980 "# " CONFIG_OPTION_FAIL_STOP " = true" NL
981 "" NL
982 "[" CONFIG_SECTION_REP_SHARING "]" NL
983 "### To conserve space, the filesystem can optionally avoid storing" NL
984 "### duplicate representations. This comes at a slight cost in" NL
985 "### performance, as maintaining a database of shared representations can" NL
986 "### increase commit times. The space savings are dependent upon the size" NL
987 "### of the repository, the number of objects it contains and the amount of" NL
988 "### duplication between them, usually a function of the branching and" NL
989 "### merging process." NL
990 "###" NL
991 "### The following parameter enables rep-sharing in the repository. It can" NL
992 "### be switched on and off at will, but for best space-saving results" NL
993 "### should be enabled consistently over the life of the repository." NL
994 "### 'svnadmin verify' will check the rep-cache regardless of this setting." NL
995 "### rep-sharing is enabled by default." NL
996 "# " CONFIG_OPTION_ENABLE_REP_SHARING " = true" NL
997 "" NL
998 "[" CONFIG_SECTION_DELTIFICATION "]" NL
999 "### To conserve space, the filesystem stores data as differences against" NL
1000 "### existing representations. This comes at a slight cost in performance," NL
1001 "### as calculating differences can increase commit times. Reading data" NL
1002 "### will also create higher CPU load and the data will be fragmented." NL
1003 "### Since deltification tends to save significant amounts of disk space," NL
1004 "### the overall I/O load can actually be lower." NL
1005 "###" NL
1006 "### The options in this section allow for tuning the deltification" NL
1007 "### strategy. Their effects on data size and server performance may vary" NL
1008 "### from one repository to another. Versions prior to 1.8 will ignore" NL
1009 "### this section." NL
1010 "###" NL
1011 "### The following parameter enables deltification for directories. It can" NL
1012 "### be switched on and off at will, but for best space-saving results" NL
1013 "### should be enabled consistently over the lifetime of the repository." NL
1014 "### Repositories containing large directories will benefit greatly." NL
1015 "### In rarely accessed repositories, the I/O overhead may be significant" NL
1016 "### as caches will most likely be low." NL
1017 "### directory deltification is enabled by default." NL
1018 "# " CONFIG_OPTION_ENABLE_DIR_DELTIFICATION " = true" NL
1019 "###" NL
1020 "### The following parameter enables deltification for properties on files" NL
1021 "### and directories. Overall, this is a minor tuning option but can save" NL
1022 "### some disk space if you merge frequently or frequently change node" NL
1023 "### properties. You should not activate this if rep-sharing has been" NL
1024 "### disabled because this may result in a net increase in repository size." NL
1025 "### property deltification is enabled by default." NL
1026 "# " CONFIG_OPTION_ENABLE_PROPS_DELTIFICATION " = true" NL
1027 "###" NL
1028 "### During commit, the server may need to walk the whole change history of" NL
1029 "### of a given node to find a suitable deltification base. This linear" NL
1030 "### process can impact commit times, svnadmin load and similar operations." NL
1031 "### This setting limits the depth of the deltification history. If the" NL
1032 "### threshold has been reached, the node will be stored as fulltext and a" NL
1033 "### new deltification history begins." NL
1034 "### Note, this is unrelated to svn log." NL
1035 "### Very large values rarely provide significant additional savings but" NL
1036 "### can impact performance greatly - in particular if directory" NL
1037 "### deltification has been activated. Very small values may be useful in" NL
1038 "### repositories that are dominated by large, changing binaries." NL
1039 "### Should be a power of two minus 1. A value of 0 will effectively" NL
1040 "### disable deltification." NL
1041 "### For 1.8, the default value is 1023; earlier versions have no limit." NL
1042 "# " CONFIG_OPTION_MAX_DELTIFICATION_WALK " = 1023" NL
1043 "###" NL
1044 "### The skip-delta scheme used by FSFS tends to repeatably store redundant" NL
1045 "### delta information where a simple delta against the latest version is" NL
1046 "### often smaller. By default, 1.8+ will therefore use skip deltas only" NL
1047 "### after the linear chain of deltas has grown beyond the threshold" NL
1048 "### specified by this setting." NL
1049 "### Values up to 64 can result in some reduction in repository size for" NL
1050 "### the cost of quickly increasing I/O and CPU costs. Similarly, smaller" NL
1051 "### numbers can reduce those costs at the cost of more disk space. For" NL
1052 "### rarely read repositories or those containing larger binaries, this may" NL
1053 "### present a better trade-off." NL
1054 "### Should be a power of two. A value of 1 or smaller will cause the" NL
1055 "### exclusive use of skip-deltas (as in pre-1.8)." NL
1056 "### For 1.8, the default value is 16; earlier versions use 1." NL
1057 "# " CONFIG_OPTION_MAX_LINEAR_DELTIFICATION " = 16" NL
1058 "###" NL
1059 "### After deltification, we compress the data to minimize on-disk size." NL
1060 "### This setting controls the compression algorithm, which will be used in" NL
1061 "### future revisions. It can be used to either disable compression or to" NL
1062 "### select between available algorithms (zlib, lz4). zlib is a general-" NL
1063 "### purpose compression algorithm. lz4 is a fast compression algorithm" NL
1064 "### which should be preferred for repositories with large and, possibly," NL
1065 "### incompressible files. Note that the compression ratio of lz4 is" NL
1066 "### usually lower than the one provided by zlib, but using it can" NL
1067 "### significantly speed up commits as well as reading the data." NL
1068 "### lz4 compression algorithm is supported, starting from format 8" NL
1069 "### repositories, available in Subversion 1.10 and higher." NL
1070 "### The syntax of this option is:" NL
1071 "### " CONFIG_OPTION_COMPRESSION " = none | lz4 | zlib | zlib-1 ... zlib-9" NL
1072 "### Versions prior to Subversion 1.10 will ignore this option." NL
1073 "### The default value is 'lz4' if supported by the repository format and" NL
1074 "### 'zlib' otherwise. 'zlib' is currently equivalent to 'zlib-5'." NL
1075 "# " CONFIG_OPTION_COMPRESSION " = lz4" NL
1076 "###" NL
1077 "### DEPRECATED: The new '" CONFIG_OPTION_COMPRESSION "' option deprecates previously used" NL
1078 "### '" CONFIG_OPTION_COMPRESSION_LEVEL "' option, which was used to configure zlib compression." NL
1079 "### For compatibility with previous versions of Subversion, this option can"NL
1080 "### still be used (and it will result in zlib compression with the" NL
1081 "### corresponding compression level)." NL
1082 "### " CONFIG_OPTION_COMPRESSION_LEVEL " = 0 ... 9 (default is 5)" NL
1083 "" NL
1084 "[" CONFIG_SECTION_PACKED_REVPROPS "]" NL
1085 "### This parameter controls the size (in kBytes) of packed revprop files." NL
1086 "### Revprops of consecutive revisions will be concatenated into a single" NL
1087 "### file up to but not exceeding the threshold given here. However, each" NL
1088 "### pack file may be much smaller and revprops of a single revision may be" NL
1089 "### much larger than the limit set here. The threshold will be applied" NL
1090 "### before optional compression takes place." NL
1091 "### Large values will reduce disk space usage at the expense of increased" NL
1092 "### latency and CPU usage reading and changing individual revprops." NL
1093 "### Values smaller than 4 kByte will not improve latency any further and " NL
1094 "### quickly render revprop packing ineffective." NL
1095 "### revprop-pack-size is 16 kBytes by default for non-compressed revprop" NL
1096 "### pack files and 64 kBytes when compression has been enabled." NL
1097 "# " CONFIG_OPTION_REVPROP_PACK_SIZE " = 16" NL
1098 "###" NL
1099 "### To save disk space, packed revprop files may be compressed. Standard" NL
1100 "### revprops tend to allow for very effective compression. Reading and" NL
1101 "### even more so writing, become significantly more CPU intensive." NL
1102 "### Compressing packed revprops is disabled by default." NL
1103 "# " CONFIG_OPTION_COMPRESS_PACKED_REVPROPS " = false" NL
1104 "" NL
1105 "[" CONFIG_SECTION_IO "]" NL
1106 "### Parameters in this section control the data access granularity in" NL
1107 "### format 7 repositories and later. The defaults should translate into" NL
1108 "### decent performance over a wide range of setups." NL
1109 "###" NL
1110 "### When a specific piece of information needs to be read from disk, a" NL
1111 "### data block is being read at once and its contents are being cached." NL
1112 "### If the repository is being stored on a RAID, the block size should be" NL
1113 "### either 50% or 100% of RAID block size / granularity. Also, your file" NL
1114 "### system blocks/clusters should be properly aligned and sized. In that" NL
1115 "### setup, each access will hit only one disk (minimizes I/O load) but" NL
1116 "### uses all the data provided by the disk in a single access." NL
1117 "### For SSD-based storage systems, slightly lower values around 16 kB" NL
1118 "### may improve latency while still maximizing throughput. If block-read" NL
1119 "### has not been enabled, this will be capped to 4 kBytes." NL
1120 "### Can be changed at any time but must be a power of 2." NL
1121 "### block-size is given in kBytes and with a default of 64 kBytes." NL
1122 "# " CONFIG_OPTION_BLOCK_SIZE " = 64" NL
1123 "###" NL
1124 "### The log-to-phys index maps data item numbers to offsets within the" NL
1125 "### rev or pack file. This index is organized in pages of a fixed maximum" NL
1126 "### capacity. To access an item, the page table and the respective page" NL
1127 "### must be read." NL
1128 "### This parameter only affects revisions with thousands of changed paths." NL
1129 "### If you have several extremely large revisions (~1 mio changes), think" NL
1130 "### about increasing this setting. Reducing the value will rarely result" NL
1131 "### in a net speedup." NL
1132 "### This is an expert setting. Must be a power of 2." NL
1133 "### l2p-page-size is 8192 entries by default." NL
1134 "# " CONFIG_OPTION_L2P_PAGE_SIZE " = 8192" NL
1135 "###" NL
1136 "### The phys-to-log index maps positions within the rev or pack file to" NL
1137 "### to data items, i.e. describes what piece of information is being" NL
1138 "### stored at any particular offset. The index describes the rev file" NL
1139 "### in chunks (pages) and keeps a global list of all those pages. Large" NL
1140 "### pages mean a shorter page table but a larger per-page description of" NL
1141 "### data items in it. The latency sweetspot depends on the change size" NL
1142 "### distribution but covers a relatively wide range." NL
1143 "### If the repository contains very large files, i.e. individual changes" NL
1144 "### of tens of MB each, increasing the page size will shorten the index" NL
1145 "### file at the expense of a slightly increased latency in sections with" NL
1146 "### smaller changes." NL
1147 "### For source code repositories, this should be about 16x the block-size." NL
1148 "### Must be a power of 2." NL
1149 "### p2l-page-size is given in kBytes and with a default of 1024 kBytes." NL
1150 "# " CONFIG_OPTION_P2L_PAGE_SIZE " = 1024" NL
1151 "" NL
1152 "[" CONFIG_SECTION_DEBUG "]" NL
1153 "###" NL
1154 "### Whether to verify each new revision immediately before finalizing" NL
1155 "### the commit. This is disabled by default except in maintainer-mode" NL
1156 "### builds." NL
1157 "# " CONFIG_OPTION_VERIFY_BEFORE_COMMIT " = false" NL
1158 ;
1159 #undef NL
1160 return svn_io_file_create(svn_dirent_join(fs->path, PATH_CONFIG, pool),
1161 fsfs_conf_contents, pool);
1162 }
1163
1164 /* Read / Evaluate the global configuration in FS->CONFIG to set up
1165 * parameters in FS. */
1166 static svn_error_t *
read_global_config(svn_fs_t * fs)1167 read_global_config(svn_fs_t *fs)
1168 {
1169 fs_fs_data_t *ffd = fs->fsap_data;
1170
1171 ffd->use_block_read = svn_hash__get_bool(fs->config,
1172 SVN_FS_CONFIG_FSFS_BLOCK_READ,
1173 FALSE);
1174 ffd->flush_to_disk = !svn_hash__get_bool(fs->config,
1175 SVN_FS_CONFIG_NO_FLUSH_TO_DISK,
1176 FALSE);
1177
1178 /* Ignore the user-specified larger block size if we don't use block-read.
1179 Defaulting to 4k gives us the same access granularity in format 7 as in
1180 older formats. */
1181 if (!ffd->use_block_read)
1182 ffd->block_size = MIN(0x1000, ffd->block_size);
1183
1184 return SVN_NO_ERROR;
1185 }
1186
1187 /* Read FS's UUID file and store the data in the FS struct. */
1188 static svn_error_t *
read_uuid(svn_fs_t * fs,apr_pool_t * scratch_pool)1189 read_uuid(svn_fs_t *fs,
1190 apr_pool_t *scratch_pool)
1191 {
1192 fs_fs_data_t *ffd = fs->fsap_data;
1193 apr_file_t *uuid_file;
1194 char buf[APR_UUID_FORMATTED_LENGTH + 2];
1195 apr_size_t limit;
1196
1197 /* Read the repository uuid. */
1198 SVN_ERR(svn_io_file_open(&uuid_file, path_uuid(fs, scratch_pool),
1199 APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
1200 scratch_pool));
1201
1202 limit = sizeof(buf);
1203 SVN_ERR(svn_io_read_length_line(uuid_file, buf, &limit, scratch_pool));
1204 fs->uuid = apr_pstrdup(fs->pool, buf);
1205
1206 /* Read the instance ID. */
1207 if (ffd->format >= SVN_FS_FS__MIN_INSTANCE_ID_FORMAT)
1208 {
1209 limit = sizeof(buf);
1210 SVN_ERR(svn_io_read_length_line(uuid_file, buf, &limit,
1211 scratch_pool));
1212 ffd->instance_id = apr_pstrdup(fs->pool, buf);
1213 }
1214 else
1215 {
1216 ffd->instance_id = fs->uuid;
1217 }
1218
1219 SVN_ERR(svn_io_file_close(uuid_file, scratch_pool));
1220
1221 return SVN_NO_ERROR;
1222 }
1223
1224 svn_error_t *
svn_fs_fs__read_format_file(svn_fs_t * fs,apr_pool_t * scratch_pool)1225 svn_fs_fs__read_format_file(svn_fs_t *fs, apr_pool_t *scratch_pool)
1226 {
1227 fs_fs_data_t *ffd = fs->fsap_data;
1228 int format, max_files_per_dir;
1229 svn_boolean_t use_log_addressing;
1230
1231 /* Read info from format file. */
1232 SVN_ERR(read_format(&format, &max_files_per_dir, &use_log_addressing,
1233 path_format(fs, scratch_pool), scratch_pool));
1234
1235 /* Now that we've got *all* info, store / update values in FFD. */
1236 ffd->format = format;
1237 ffd->max_files_per_dir = max_files_per_dir;
1238 ffd->use_log_addressing = use_log_addressing;
1239
1240 return SVN_NO_ERROR;
1241 }
1242
1243 svn_error_t *
svn_fs_fs__open(svn_fs_t * fs,const char * path,apr_pool_t * pool)1244 svn_fs_fs__open(svn_fs_t *fs, const char *path, apr_pool_t *pool)
1245 {
1246 fs_fs_data_t *ffd = fs->fsap_data;
1247 fs->path = apr_pstrdup(fs->pool, path);
1248
1249 /* Read the FS format file. */
1250 SVN_ERR(svn_fs_fs__read_format_file(fs, pool));
1251
1252 /* Read in and cache the repository uuid. */
1253 SVN_ERR(read_uuid(fs, pool));
1254
1255 /* Read the min unpacked revision. */
1256 if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
1257 SVN_ERR(svn_fs_fs__update_min_unpacked_rev(fs, pool));
1258
1259 /* Read the configuration file. */
1260 SVN_ERR(read_config(ffd, fs->path, fs->pool, pool));
1261
1262 /* Global configuration options. */
1263 SVN_ERR(read_global_config(fs));
1264
1265 ffd->youngest_rev_cache = 0;
1266
1267 return SVN_NO_ERROR;
1268 }
1269
1270 /* Wrapper around svn_io_file_create which ignores EEXIST. */
1271 static svn_error_t *
create_file_ignore_eexist(const char * file,const char * contents,apr_pool_t * pool)1272 create_file_ignore_eexist(const char *file,
1273 const char *contents,
1274 apr_pool_t *pool)
1275 {
1276 svn_error_t *err = svn_io_file_create(file, contents, pool);
1277 if (err && APR_STATUS_IS_EEXIST(err->apr_err))
1278 {
1279 svn_error_clear(err);
1280 err = SVN_NO_ERROR;
1281 }
1282 return svn_error_trace(err);
1283 }
1284
1285 /* Baton type bridging svn_fs_fs__upgrade and upgrade_body carrying
1286 * parameters over between them. */
1287 struct upgrade_baton_t
1288 {
1289 svn_fs_t *fs;
1290 svn_fs_upgrade_notify_t notify_func;
1291 void *notify_baton;
1292 svn_cancel_func_t cancel_func;
1293 void *cancel_baton;
1294 };
1295
1296 static svn_error_t *
upgrade_body(void * baton,apr_pool_t * pool)1297 upgrade_body(void *baton, apr_pool_t *pool)
1298 {
1299 struct upgrade_baton_t *upgrade_baton = baton;
1300 svn_fs_t *fs = upgrade_baton->fs;
1301 fs_fs_data_t *ffd = fs->fsap_data;
1302 int format, max_files_per_dir;
1303 svn_boolean_t use_log_addressing;
1304 const char *format_path = path_format(fs, pool);
1305 svn_node_kind_t kind;
1306 svn_boolean_t needs_revprop_shard_cleanup = FALSE;
1307
1308 /* Read the FS format number and max-files-per-dir setting. */
1309 SVN_ERR(read_format(&format, &max_files_per_dir, &use_log_addressing,
1310 format_path, pool));
1311
1312 /* If the config file does not exist, create one. */
1313 SVN_ERR(svn_io_check_path(svn_dirent_join(fs->path, PATH_CONFIG, pool),
1314 &kind, pool));
1315 switch (kind)
1316 {
1317 case svn_node_none:
1318 SVN_ERR(write_config(fs, pool));
1319 break;
1320 case svn_node_file:
1321 break;
1322 default:
1323 return svn_error_createf(SVN_ERR_FS_GENERAL, NULL,
1324 _("'%s' is not a regular file."
1325 " Please move it out of "
1326 "the way and try again"),
1327 svn_dirent_join(fs->path, PATH_CONFIG, pool));
1328 }
1329
1330 /* If we're already up-to-date, there's nothing else to be done here. */
1331 if (format == SVN_FS_FS__FORMAT_NUMBER)
1332 return SVN_NO_ERROR;
1333
1334 /* If our filesystem predates the existence of the 'txn-current
1335 file', make that file and its corresponding lock file. */
1336 if (format < SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
1337 {
1338 SVN_ERR(create_file_ignore_eexist(
1339 svn_fs_fs__path_txn_current(fs, pool), "0\n",
1340 pool));
1341 SVN_ERR(create_file_ignore_eexist(
1342 svn_fs_fs__path_txn_current_lock(fs, pool), "",
1343 pool));
1344 }
1345
1346 /* If our filesystem predates the existence of the 'txn-protorevs'
1347 dir, make that directory. */
1348 if (format < SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
1349 {
1350 SVN_ERR(svn_io_make_dir_recursively(
1351 svn_fs_fs__path_txn_proto_revs(fs, pool), pool));
1352 }
1353
1354 /* If our filesystem is new enough, write the min unpacked rev file. */
1355 if (format < SVN_FS_FS__MIN_PACKED_FORMAT)
1356 SVN_ERR(svn_io_file_create(svn_fs_fs__path_min_unpacked_rev(fs, pool),
1357 "0\n", pool));
1358
1359 /* If the file system supports revision packing but not revprop packing
1360 *and* the FS has been sharded, pack the revprops up to the point that
1361 revision data has been packed. However, keep the non-packed revprop
1362 files around until after the format bump */
1363 if ( format >= SVN_FS_FS__MIN_PACKED_FORMAT
1364 && format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT
1365 && max_files_per_dir > 0)
1366 {
1367 needs_revprop_shard_cleanup = TRUE;
1368 SVN_ERR(svn_fs_fs__upgrade_pack_revprops(fs,
1369 upgrade_baton->notify_func,
1370 upgrade_baton->notify_baton,
1371 upgrade_baton->cancel_func,
1372 upgrade_baton->cancel_baton,
1373 pool));
1374 }
1375
1376 /* We will need the UUID info shortly ...
1377 Read it before the format bump as the UUID file still uses the old
1378 format. */
1379 SVN_ERR(read_uuid(fs, pool));
1380
1381 /* Update the format info in the FS struct. Upgrade steps further
1382 down will use the format from FS to create missing info. */
1383 ffd->format = SVN_FS_FS__FORMAT_NUMBER;
1384 ffd->max_files_per_dir = max_files_per_dir;
1385 ffd->use_log_addressing = use_log_addressing;
1386
1387 /* Always add / bump the instance ID such that no form of caching
1388 accidentally uses outdated information. Keep the UUID. */
1389 SVN_ERR(svn_fs_fs__set_uuid(fs, fs->uuid, NULL, pool));
1390
1391 /* Bump the format file. */
1392 SVN_ERR(svn_fs_fs__write_format(fs, TRUE, pool));
1393
1394 if (upgrade_baton->notify_func)
1395 SVN_ERR(upgrade_baton->notify_func(upgrade_baton->notify_baton,
1396 SVN_FS_FS__FORMAT_NUMBER,
1397 svn_fs_upgrade_format_bumped,
1398 pool));
1399
1400 /* Now, it is safe to remove the redundant revprop files. */
1401 if (needs_revprop_shard_cleanup)
1402 SVN_ERR(svn_fs_fs__upgrade_cleanup_pack_revprops(fs,
1403 upgrade_baton->notify_func,
1404 upgrade_baton->notify_baton,
1405 upgrade_baton->cancel_func,
1406 upgrade_baton->cancel_baton,
1407 pool));
1408
1409 /* Done */
1410 return SVN_NO_ERROR;
1411 }
1412
1413
1414 svn_error_t *
svn_fs_fs__upgrade(svn_fs_t * fs,svn_fs_upgrade_notify_t notify_func,void * notify_baton,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * pool)1415 svn_fs_fs__upgrade(svn_fs_t *fs,
1416 svn_fs_upgrade_notify_t notify_func,
1417 void *notify_baton,
1418 svn_cancel_func_t cancel_func,
1419 void *cancel_baton,
1420 apr_pool_t *pool)
1421 {
1422 struct upgrade_baton_t baton;
1423 baton.fs = fs;
1424 baton.notify_func = notify_func;
1425 baton.notify_baton = notify_baton;
1426 baton.cancel_func = cancel_func;
1427 baton.cancel_baton = cancel_baton;
1428
1429 return svn_fs_fs__with_all_locks(fs, upgrade_body, (void *)&baton, pool);
1430 }
1431
1432 /* Find the youngest revision in a repository at path FS_PATH and
1433 return it in *YOUNGEST_P. Perform temporary allocations in
1434 POOL. */
1435 static svn_error_t *
get_youngest(svn_revnum_t * youngest_p,svn_fs_t * fs,apr_pool_t * pool)1436 get_youngest(svn_revnum_t *youngest_p,
1437 svn_fs_t *fs,
1438 apr_pool_t *pool)
1439 {
1440 apr_uint64_t dummy;
1441 SVN_ERR(svn_fs_fs__read_current(youngest_p, &dummy, &dummy, fs, pool));
1442 return SVN_NO_ERROR;
1443 }
1444
1445
1446 svn_error_t *
svn_fs_fs__youngest_rev(svn_revnum_t * youngest_p,svn_fs_t * fs,apr_pool_t * pool)1447 svn_fs_fs__youngest_rev(svn_revnum_t *youngest_p,
1448 svn_fs_t *fs,
1449 apr_pool_t *pool)
1450 {
1451 fs_fs_data_t *ffd = fs->fsap_data;
1452
1453 SVN_ERR(get_youngest(youngest_p, fs, pool));
1454 ffd->youngest_rev_cache = *youngest_p;
1455
1456 return SVN_NO_ERROR;
1457 }
1458
1459 int
svn_fs_fs__shard_size(svn_fs_t * fs)1460 svn_fs_fs__shard_size(svn_fs_t *fs)
1461 {
1462 fs_fs_data_t *ffd = fs->fsap_data;
1463
1464 return ffd->max_files_per_dir;
1465 }
1466
1467 svn_error_t *
svn_fs_fs__min_unpacked_rev(svn_revnum_t * min_unpacked,svn_fs_t * fs,apr_pool_t * pool)1468 svn_fs_fs__min_unpacked_rev(svn_revnum_t *min_unpacked,
1469 svn_fs_t *fs,
1470 apr_pool_t *pool)
1471 {
1472 fs_fs_data_t *ffd = fs->fsap_data;
1473
1474 /* Calling this for pre-v4 repos is illegal. */
1475 if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
1476 SVN_ERR(svn_fs_fs__update_min_unpacked_rev(fs, pool));
1477
1478 *min_unpacked = ffd->min_unpacked_rev;
1479
1480 return SVN_NO_ERROR;
1481 }
1482
1483 svn_error_t *
svn_fs_fs__ensure_revision_exists(svn_revnum_t rev,svn_fs_t * fs,apr_pool_t * pool)1484 svn_fs_fs__ensure_revision_exists(svn_revnum_t rev,
1485 svn_fs_t *fs,
1486 apr_pool_t *pool)
1487 {
1488 fs_fs_data_t *ffd = fs->fsap_data;
1489
1490 if (! SVN_IS_VALID_REVNUM(rev))
1491 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1492 _("Invalid revision number '%ld'"), rev);
1493
1494
1495 /* Did the revision exist the last time we checked the current
1496 file? */
1497 if (rev <= ffd->youngest_rev_cache)
1498 return SVN_NO_ERROR;
1499
1500 SVN_ERR(get_youngest(&(ffd->youngest_rev_cache), fs, pool));
1501
1502 /* Check again. */
1503 if (rev <= ffd->youngest_rev_cache)
1504 return SVN_NO_ERROR;
1505
1506 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1507 _("No such revision %ld"), rev);
1508 }
1509
1510 svn_error_t *
svn_fs_fs__file_length(svn_filesize_t * length,node_revision_t * noderev,apr_pool_t * pool)1511 svn_fs_fs__file_length(svn_filesize_t *length,
1512 node_revision_t *noderev,
1513 apr_pool_t *pool)
1514 {
1515 representation_t *data_rep = noderev->data_rep;
1516 if (!data_rep)
1517 {
1518 /* Treat "no representation" as "empty file". */
1519 *length = 0;
1520 }
1521 else
1522 {
1523 *length = data_rep->expanded_size;
1524 }
1525
1526 return SVN_NO_ERROR;
1527 }
1528
1529 svn_boolean_t
svn_fs_fs__noderev_same_rep_key(representation_t * a,representation_t * b)1530 svn_fs_fs__noderev_same_rep_key(representation_t *a,
1531 representation_t *b)
1532 {
1533 if (a == b)
1534 return TRUE;
1535
1536 if (a == NULL || b == NULL)
1537 return FALSE;
1538
1539 if (a->item_index != b->item_index)
1540 return FALSE;
1541
1542 if (a->revision != b->revision)
1543 return FALSE;
1544
1545 return memcmp(&a->uniquifier, &b->uniquifier, sizeof(a->uniquifier)) == 0;
1546 }
1547
1548 svn_error_t *
svn_fs_fs__file_text_rep_equal(svn_boolean_t * equal,svn_fs_t * fs,node_revision_t * a,node_revision_t * b,apr_pool_t * scratch_pool)1549 svn_fs_fs__file_text_rep_equal(svn_boolean_t *equal,
1550 svn_fs_t *fs,
1551 node_revision_t *a,
1552 node_revision_t *b,
1553 apr_pool_t *scratch_pool)
1554 {
1555 svn_stream_t *contents_a, *contents_b;
1556 representation_t *rep_a = a->data_rep;
1557 representation_t *rep_b = b->data_rep;
1558 svn_boolean_t a_empty = !rep_a || rep_a->expanded_size == 0;
1559 svn_boolean_t b_empty = !rep_b || rep_b->expanded_size == 0;
1560
1561 /* This makes sure that neither rep will be NULL later on */
1562 if (a_empty && b_empty)
1563 {
1564 *equal = TRUE;
1565 return SVN_NO_ERROR;
1566 }
1567
1568 if (a_empty != b_empty)
1569 {
1570 *equal = FALSE;
1571 return SVN_NO_ERROR;
1572 }
1573
1574 /* File text representations always know their checksums - even in a txn. */
1575 if (memcmp(rep_a->md5_digest, rep_b->md5_digest, sizeof(rep_a->md5_digest)))
1576 {
1577 *equal = FALSE;
1578 return SVN_NO_ERROR;
1579 }
1580
1581 /* Paranoia. Compare SHA1 checksums because that's the level of
1582 confidence we require for e.g. the working copy. */
1583 if (rep_a->has_sha1 && rep_b->has_sha1)
1584 {
1585 *equal = memcmp(rep_a->sha1_digest, rep_b->sha1_digest,
1586 sizeof(rep_a->sha1_digest)) == 0;
1587 return SVN_NO_ERROR;
1588 }
1589
1590 /* Same path in same rev or txn? */
1591 if (svn_fs_fs__id_eq(a->id, b->id))
1592 {
1593 *equal = TRUE;
1594 return SVN_NO_ERROR;
1595 }
1596
1597 SVN_ERR(svn_fs_fs__get_contents(&contents_a, fs, rep_a, TRUE,
1598 scratch_pool));
1599 SVN_ERR(svn_fs_fs__get_contents(&contents_b, fs, rep_b, TRUE,
1600 scratch_pool));
1601 SVN_ERR(svn_stream_contents_same2(equal, contents_a, contents_b,
1602 scratch_pool));
1603
1604 return SVN_NO_ERROR;
1605 }
1606
1607 svn_error_t *
svn_fs_fs__prop_rep_equal(svn_boolean_t * equal,svn_fs_t * fs,node_revision_t * a,node_revision_t * b,apr_pool_t * scratch_pool)1608 svn_fs_fs__prop_rep_equal(svn_boolean_t *equal,
1609 svn_fs_t *fs,
1610 node_revision_t *a,
1611 node_revision_t *b,
1612 apr_pool_t *scratch_pool)
1613 {
1614 representation_t *rep_a = a->prop_rep;
1615 representation_t *rep_b = b->prop_rep;
1616 apr_hash_t *proplist_a;
1617 apr_hash_t *proplist_b;
1618
1619 /* Mainly for a==b==NULL */
1620 if (rep_a == rep_b)
1621 {
1622 *equal = TRUE;
1623 return SVN_NO_ERROR;
1624 }
1625
1626 /* Committed property lists can be compared quickly */
1627 if ( rep_a && rep_b
1628 && !svn_fs_fs__id_txn_used(&rep_a->txn_id)
1629 && !svn_fs_fs__id_txn_used(&rep_b->txn_id))
1630 {
1631 /* Same representation? */
1632 if ( (rep_a->revision == rep_b->revision)
1633 && (rep_a->item_index == rep_b->item_index))
1634 {
1635 *equal = TRUE;
1636 return SVN_NO_ERROR;
1637 }
1638
1639 /* Known different content? MD5 must be given. */
1640 if (memcmp(rep_a->md5_digest, rep_b->md5_digest,
1641 sizeof(rep_a->md5_digest)))
1642 {
1643 *equal = FALSE;
1644 return SVN_NO_ERROR;
1645 }
1646 }
1647
1648 /* Same path in same txn?
1649 *
1650 * For committed reps, IDs cannot be the same here b/c we already know
1651 * that they point to different representations. */
1652 if (svn_fs_fs__id_eq(a->id, b->id))
1653 {
1654 *equal = TRUE;
1655 return SVN_NO_ERROR;
1656 }
1657
1658 /* At least one of the reps has been modified in a txn.
1659 Fetch and compare them. */
1660 SVN_ERR(svn_fs_fs__get_proplist(&proplist_a, fs, a, scratch_pool));
1661 SVN_ERR(svn_fs_fs__get_proplist(&proplist_b, fs, b, scratch_pool));
1662
1663 *equal = svn_fs__prop_lists_equal(proplist_a, proplist_b, scratch_pool);
1664 return SVN_NO_ERROR;
1665 }
1666
1667
1668 svn_error_t *
svn_fs_fs__file_checksum(svn_checksum_t ** checksum,node_revision_t * noderev,svn_checksum_kind_t kind,apr_pool_t * pool)1669 svn_fs_fs__file_checksum(svn_checksum_t **checksum,
1670 node_revision_t *noderev,
1671 svn_checksum_kind_t kind,
1672 apr_pool_t *pool)
1673 {
1674 *checksum = NULL;
1675
1676 if (noderev->data_rep)
1677 {
1678 svn_checksum_t temp;
1679 temp.kind = kind;
1680
1681 switch(kind)
1682 {
1683 case svn_checksum_md5:
1684 temp.digest = noderev->data_rep->md5_digest;
1685 break;
1686
1687 case svn_checksum_sha1:
1688 if (! noderev->data_rep->has_sha1)
1689 return SVN_NO_ERROR;
1690
1691 temp.digest = noderev->data_rep->sha1_digest;
1692 break;
1693
1694 default:
1695 return SVN_NO_ERROR;
1696 }
1697
1698 *checksum = svn_checksum_dup(&temp, pool);
1699 }
1700
1701 return SVN_NO_ERROR;
1702 }
1703
1704 representation_t *
svn_fs_fs__rep_copy(representation_t * rep,apr_pool_t * pool)1705 svn_fs_fs__rep_copy(representation_t *rep,
1706 apr_pool_t *pool)
1707 {
1708 if (rep == NULL)
1709 return NULL;
1710
1711 return apr_pmemdup(pool, rep, sizeof(*rep));
1712 }
1713
1714
1715 /* Write out the zeroth revision for filesystem FS.
1716 Perform temporary allocations in SCRATCH_POOL. */
1717 static svn_error_t *
write_revision_zero(svn_fs_t * fs,apr_pool_t * scratch_pool)1718 write_revision_zero(svn_fs_t *fs,
1719 apr_pool_t *scratch_pool)
1720 {
1721 /* Use an explicit sub-pool to have full control over temp file lifetimes.
1722 * Since we have it, use it for everything else as well. */
1723 apr_pool_t *subpool = svn_pool_create(scratch_pool);
1724 const char *path_revision_zero = svn_fs_fs__path_rev(fs, 0, subpool);
1725 apr_hash_t *proplist;
1726 svn_string_t date;
1727
1728 /* Write out a rev file for revision 0. */
1729 if (svn_fs_fs__use_log_addressing(fs))
1730 {
1731 apr_array_header_t *index_entries;
1732 svn_fs_fs__p2l_entry_t *entry;
1733 svn_fs_fs__revision_file_t *rev_file;
1734 const char *l2p_proto_index, *p2l_proto_index;
1735
1736 /* Write a skeleton r0 with no indexes. */
1737 SVN_ERR(svn_io_file_create(path_revision_zero,
1738 "PLAIN\nEND\nENDREP\n"
1739 "id: 0.0.r0/2\n"
1740 "type: dir\n"
1741 "count: 0\n"
1742 "text: 0 3 4 4 "
1743 "2d2977d1c96f487abe4a1e202dd03b4e\n"
1744 "cpath: /\n"
1745 "\n\n", subpool));
1746
1747 /* Construct the index P2L contents: describe the 3 items we have.
1748 Be sure to create them in on-disk order. */
1749 index_entries = apr_array_make(subpool, 3, sizeof(entry));
1750
1751 entry = apr_pcalloc(subpool, sizeof(*entry));
1752 entry->offset = 0;
1753 entry->size = 17;
1754 entry->type = SVN_FS_FS__ITEM_TYPE_DIR_REP;
1755 entry->item.revision = 0;
1756 entry->item.number = SVN_FS_FS__ITEM_INDEX_FIRST_USER;
1757 APR_ARRAY_PUSH(index_entries, svn_fs_fs__p2l_entry_t *) = entry;
1758
1759 entry = apr_pcalloc(subpool, sizeof(*entry));
1760 entry->offset = 17;
1761 entry->size = 89;
1762 entry->type = SVN_FS_FS__ITEM_TYPE_NODEREV;
1763 entry->item.revision = 0;
1764 entry->item.number = SVN_FS_FS__ITEM_INDEX_ROOT_NODE;
1765 APR_ARRAY_PUSH(index_entries, svn_fs_fs__p2l_entry_t *) = entry;
1766
1767 entry = apr_pcalloc(subpool, sizeof(*entry));
1768 entry->offset = 106;
1769 entry->size = 1;
1770 entry->type = SVN_FS_FS__ITEM_TYPE_CHANGES;
1771 entry->item.revision = 0;
1772 entry->item.number = SVN_FS_FS__ITEM_INDEX_CHANGES;
1773 APR_ARRAY_PUSH(index_entries, svn_fs_fs__p2l_entry_t *) = entry;
1774
1775 /* Now re-open r0, create proto-index files from our entries and
1776 rewrite the index section of r0. */
1777 SVN_ERR(svn_fs_fs__open_pack_or_rev_file_writable(&rev_file, fs, 0,
1778 subpool, subpool));
1779 SVN_ERR(svn_fs_fs__p2l_index_from_p2l_entries(&p2l_proto_index, fs,
1780 rev_file, index_entries,
1781 subpool, subpool));
1782 SVN_ERR(svn_fs_fs__l2p_index_from_p2l_entries(&l2p_proto_index, fs,
1783 index_entries,
1784 subpool, subpool));
1785 SVN_ERR(svn_fs_fs__add_index_data(fs, rev_file->file, l2p_proto_index,
1786 p2l_proto_index, 0, subpool));
1787 SVN_ERR(svn_fs_fs__close_revision_file(rev_file));
1788 }
1789 else
1790 SVN_ERR(svn_io_file_create(path_revision_zero,
1791 "PLAIN\nEND\nENDREP\n"
1792 "id: 0.0.r0/17\n"
1793 "type: dir\n"
1794 "count: 0\n"
1795 "text: 0 0 4 4 "
1796 "2d2977d1c96f487abe4a1e202dd03b4e\n"
1797 "cpath: /\n"
1798 "\n\n17 107\n", subpool));
1799
1800 SVN_ERR(svn_io_set_file_read_only(path_revision_zero, FALSE, subpool));
1801
1802 /* Set a date on revision 0. */
1803 date.data = svn_time_to_cstring(apr_time_now(), subpool);
1804 date.len = strlen(date.data);
1805 proplist = apr_hash_make(subpool);
1806 svn_hash_sets(proplist, SVN_PROP_REVISION_DATE, &date);
1807 SVN_ERR(svn_fs_fs__set_revision_proplist(fs, 0, proplist, subpool));
1808
1809 svn_pool_destroy(subpool);
1810 return SVN_NO_ERROR;
1811 }
1812
1813 svn_error_t *
svn_fs_fs__create_file_tree(svn_fs_t * fs,const char * path,int format,int shard_size,svn_boolean_t use_log_addressing,apr_pool_t * pool)1814 svn_fs_fs__create_file_tree(svn_fs_t *fs,
1815 const char *path,
1816 int format,
1817 int shard_size,
1818 svn_boolean_t use_log_addressing,
1819 apr_pool_t *pool)
1820 {
1821 fs_fs_data_t *ffd = fs->fsap_data;
1822
1823 fs->path = apr_pstrdup(fs->pool, path);
1824 ffd->format = format;
1825
1826 /* Use an appropriate sharding mode if supported by the format. */
1827 if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
1828 ffd->max_files_per_dir = shard_size;
1829 else
1830 ffd->max_files_per_dir = 0;
1831
1832 /* Select the addressing mode depending on the format. */
1833 if (format >= SVN_FS_FS__MIN_LOG_ADDRESSING_FORMAT)
1834 ffd->use_log_addressing = use_log_addressing;
1835 else
1836 ffd->use_log_addressing = FALSE;
1837
1838 /* Create the revision data directories. */
1839 if (ffd->max_files_per_dir)
1840 SVN_ERR(svn_io_make_dir_recursively(svn_fs_fs__path_rev_shard(fs, 0,
1841 pool),
1842 pool));
1843 else
1844 SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_REVS_DIR,
1845 pool),
1846 pool));
1847
1848 /* Create the revprops directory. */
1849 if (ffd->max_files_per_dir)
1850 SVN_ERR(svn_io_make_dir_recursively(svn_fs_fs__path_revprops_shard(fs, 0,
1851 pool),
1852 pool));
1853 else
1854 SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path,
1855 PATH_REVPROPS_DIR,
1856 pool),
1857 pool));
1858
1859 /* Create the transaction directory. */
1860 SVN_ERR(svn_io_make_dir_recursively(svn_fs_fs__path_txns_dir(fs, pool),
1861 pool));
1862
1863 /* Create the protorevs directory. */
1864 if (format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
1865 SVN_ERR(svn_io_make_dir_recursively(svn_fs_fs__path_txn_proto_revs(fs,
1866 pool),
1867 pool));
1868
1869 /* Create the 'current' file. */
1870 SVN_ERR(svn_io_file_create_empty(svn_fs_fs__path_current(fs, pool), pool));
1871 SVN_ERR(svn_fs_fs__write_current(fs, 0, 1, 1, pool));
1872
1873 /* Create the 'uuid' file. */
1874 SVN_ERR(svn_io_file_create_empty(svn_fs_fs__path_lock(fs, pool), pool));
1875 SVN_ERR(svn_fs_fs__set_uuid(fs, NULL, NULL, pool));
1876
1877 /* Create the fsfs.conf file if supported. Older server versions would
1878 simply ignore the file but that might result in a different behavior
1879 than with the later releases. Also, hotcopy would ignore, i.e. not
1880 copy, a fsfs.conf with old formats. */
1881 if (ffd->format >= SVN_FS_FS__MIN_CONFIG_FILE)
1882 SVN_ERR(write_config(fs, pool));
1883
1884 SVN_ERR(read_config(ffd, fs->path, fs->pool, pool));
1885
1886 /* Global configuration options. */
1887 SVN_ERR(read_global_config(fs));
1888
1889 /* Add revision 0. */
1890 SVN_ERR(write_revision_zero(fs, pool));
1891
1892 /* Create the min unpacked rev file. */
1893 if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
1894 SVN_ERR(svn_io_file_create(svn_fs_fs__path_min_unpacked_rev(fs, pool),
1895 "0\n", pool));
1896
1897 /* Create the txn-current file if the repository supports
1898 the transaction sequence file. */
1899 if (format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
1900 {
1901 SVN_ERR(svn_io_file_create(svn_fs_fs__path_txn_current(fs, pool),
1902 "0\n", pool));
1903 SVN_ERR(svn_io_file_create_empty(
1904 svn_fs_fs__path_txn_current_lock(fs, pool),
1905 pool));
1906 }
1907
1908 ffd->youngest_rev_cache = 0;
1909 return SVN_NO_ERROR;
1910 }
1911
1912 svn_error_t *
svn_fs_fs__create(svn_fs_t * fs,const char * path,apr_pool_t * pool)1913 svn_fs_fs__create(svn_fs_t *fs,
1914 const char *path,
1915 apr_pool_t *pool)
1916 {
1917 int format = SVN_FS_FS__FORMAT_NUMBER;
1918 int shard_size = SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR;
1919 svn_boolean_t log_addressing;
1920
1921 /* Process the given filesystem config. */
1922 if (fs->config)
1923 {
1924 svn_version_t *compatible_version;
1925 const char *shard_size_str;
1926 SVN_ERR(svn_fs__compatible_version(&compatible_version, fs->config,
1927 pool));
1928
1929 /* select format number */
1930 switch(compatible_version->minor)
1931 {
1932 case 0: return svn_error_create(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
1933 _("FSFS is not compatible with Subversion prior to 1.1"));
1934
1935 case 1:
1936 case 2:
1937 case 3: format = 1;
1938 break;
1939
1940 case 4: format = 2;
1941 break;
1942
1943 case 5: format = 3;
1944 break;
1945
1946 case 6:
1947 case 7: format = 4;
1948 break;
1949
1950 case 8: format = 6;
1951 break;
1952 case 9: format = 7;
1953 break;
1954
1955 default:format = SVN_FS_FS__FORMAT_NUMBER;
1956 }
1957
1958 shard_size_str = svn_hash_gets(fs->config, SVN_FS_CONFIG_FSFS_SHARD_SIZE);
1959 if (shard_size_str)
1960 {
1961 apr_int64_t val;
1962 SVN_ERR(svn_cstring_strtoi64(&val, shard_size_str, 0,
1963 APR_INT32_MAX, 10));
1964
1965 shard_size = (int) val;
1966 }
1967 }
1968
1969 log_addressing = svn_hash__get_bool(fs->config,
1970 SVN_FS_CONFIG_FSFS_LOG_ADDRESSING,
1971 TRUE);
1972
1973 /* Actual FS creation. */
1974 SVN_ERR(svn_fs_fs__create_file_tree(fs, path, format, shard_size,
1975 log_addressing, pool));
1976
1977 /* This filesystem is ready. Stamp it with a format number. */
1978 SVN_ERR(svn_fs_fs__write_format(fs, FALSE, pool));
1979
1980 return SVN_NO_ERROR;
1981 }
1982
1983 svn_error_t *
svn_fs_fs__set_uuid(svn_fs_t * fs,const char * uuid,const char * instance_id,apr_pool_t * pool)1984 svn_fs_fs__set_uuid(svn_fs_t *fs,
1985 const char *uuid,
1986 const char *instance_id,
1987 apr_pool_t *pool)
1988 {
1989 fs_fs_data_t *ffd = fs->fsap_data;
1990 const char *uuid_path = path_uuid(fs, pool);
1991 svn_stringbuf_t *contents = svn_stringbuf_create_empty(pool);
1992
1993 if (! uuid)
1994 uuid = svn_uuid_generate(pool);
1995
1996 if (! instance_id)
1997 instance_id = svn_uuid_generate(pool);
1998
1999 svn_stringbuf_appendcstr(contents, uuid);
2000 svn_stringbuf_appendcstr(contents, "\n");
2001
2002 if (ffd->format >= SVN_FS_FS__MIN_INSTANCE_ID_FORMAT)
2003 {
2004 svn_stringbuf_appendcstr(contents, instance_id);
2005 svn_stringbuf_appendcstr(contents, "\n");
2006 }
2007
2008 /* We use the permissions of the 'current' file, because the 'uuid'
2009 file does not exist during repository creation. */
2010 SVN_ERR(svn_io_write_atomic2(uuid_path, contents->data, contents->len,
2011 svn_fs_fs__path_current(fs, pool) /* perms */,
2012 ffd->flush_to_disk, pool));
2013
2014 fs->uuid = apr_pstrdup(fs->pool, uuid);
2015
2016 if (ffd->format >= SVN_FS_FS__MIN_INSTANCE_ID_FORMAT)
2017 ffd->instance_id = apr_pstrdup(fs->pool, instance_id);
2018 else
2019 ffd->instance_id = fs->uuid;
2020
2021 return SVN_NO_ERROR;
2022 }
2023
2024 /** Node origin lazy cache. */
2025
2026 /* If directory PATH does not exist, create it and give it the same
2027 permissions as FS_path.*/
2028 svn_error_t *
svn_fs_fs__ensure_dir_exists(const char * path,const char * fs_path,apr_pool_t * pool)2029 svn_fs_fs__ensure_dir_exists(const char *path,
2030 const char *fs_path,
2031 apr_pool_t *pool)
2032 {
2033 svn_error_t *err = svn_io_dir_make(path, APR_OS_DEFAULT, pool);
2034 if (err && APR_STATUS_IS_EEXIST(err->apr_err))
2035 {
2036 svn_error_clear(err);
2037 return SVN_NO_ERROR;
2038 }
2039 SVN_ERR(err);
2040
2041 /* We successfully created a new directory. Dup the permissions
2042 from FS->path. */
2043 return svn_io_copy_perms(fs_path, path, pool);
2044 }
2045
2046 /* Set *NODE_ORIGINS to a hash mapping 'const char *' node IDs to
2047 'svn_string_t *' node revision IDs. Use POOL for allocations. */
2048 static svn_error_t *
get_node_origins_from_file(svn_fs_t * fs,apr_hash_t ** node_origins,const char * node_origins_file,apr_pool_t * pool)2049 get_node_origins_from_file(svn_fs_t *fs,
2050 apr_hash_t **node_origins,
2051 const char *node_origins_file,
2052 apr_pool_t *pool)
2053 {
2054 apr_file_t *fd;
2055 svn_error_t *err;
2056 svn_stream_t *stream;
2057
2058 *node_origins = NULL;
2059 err = svn_io_file_open(&fd, node_origins_file,
2060 APR_READ, APR_OS_DEFAULT, pool);
2061 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
2062 {
2063 svn_error_clear(err);
2064 return SVN_NO_ERROR;
2065 }
2066 SVN_ERR(err);
2067
2068 stream = svn_stream_from_aprfile2(fd, FALSE, pool);
2069 *node_origins = apr_hash_make(pool);
2070 err = svn_hash_read2(*node_origins, stream, SVN_HASH_TERMINATOR, pool);
2071 if (err)
2072 return svn_error_quick_wrapf(err, _("malformed node origin data in '%s'"),
2073 node_origins_file);
2074 return svn_stream_close(stream);
2075 }
2076
2077 svn_error_t *
svn_fs_fs__get_node_origin(const svn_fs_id_t ** origin_id,svn_fs_t * fs,const svn_fs_fs__id_part_t * node_id,apr_pool_t * pool)2078 svn_fs_fs__get_node_origin(const svn_fs_id_t **origin_id,
2079 svn_fs_t *fs,
2080 const svn_fs_fs__id_part_t *node_id,
2081 apr_pool_t *pool)
2082 {
2083 apr_hash_t *node_origins;
2084
2085 *origin_id = NULL;
2086 SVN_ERR(get_node_origins_from_file(fs, &node_origins,
2087 svn_fs_fs__path_node_origin(fs, node_id,
2088 pool),
2089 pool));
2090 if (node_origins)
2091 {
2092 char node_id_ptr[SVN_INT64_BUFFER_SIZE];
2093 apr_size_t len = svn__ui64tobase36(node_id_ptr, node_id->number);
2094 svn_string_t *origin_id_str
2095 = apr_hash_get(node_origins, node_id_ptr, len);
2096
2097 if (origin_id_str)
2098 SVN_ERR(svn_fs_fs__id_parse(origin_id,
2099 apr_pstrdup(pool, origin_id_str->data),
2100 pool));
2101 }
2102 return SVN_NO_ERROR;
2103 }
2104
2105
2106 /* Helper for svn_fs_fs__set_node_origin. Takes a NODE_ID/NODE_REV_ID
2107 pair and adds it to the NODE_ORIGINS_PATH file. */
2108 static svn_error_t *
set_node_origins_for_file(svn_fs_t * fs,const char * node_origins_path,const svn_fs_fs__id_part_t * node_id,svn_string_t * node_rev_id,apr_pool_t * pool)2109 set_node_origins_for_file(svn_fs_t *fs,
2110 const char *node_origins_path,
2111 const svn_fs_fs__id_part_t *node_id,
2112 svn_string_t *node_rev_id,
2113 apr_pool_t *pool)
2114 {
2115 const char *path_tmp;
2116 svn_stream_t *stream;
2117 apr_hash_t *origins_hash;
2118 svn_string_t *old_node_rev_id;
2119
2120 /* the hash serialization functions require strings as keys */
2121 char node_id_ptr[SVN_INT64_BUFFER_SIZE];
2122 apr_size_t len = svn__ui64tobase36(node_id_ptr, node_id->number);
2123
2124 SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_join(fs->path,
2125 PATH_NODE_ORIGINS_DIR,
2126 pool),
2127 fs->path, pool));
2128
2129 /* Read the previously existing origins (if any), and merge our
2130 update with it. */
2131 SVN_ERR(get_node_origins_from_file(fs, &origins_hash,
2132 node_origins_path, pool));
2133 if (! origins_hash)
2134 origins_hash = apr_hash_make(pool);
2135
2136 old_node_rev_id = apr_hash_get(origins_hash, node_id_ptr, len);
2137
2138 if (old_node_rev_id && !svn_string_compare(node_rev_id, old_node_rev_id))
2139 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2140 _("Node origin for '%s' exists with a different "
2141 "value (%s) than what we were about to store "
2142 "(%s)"),
2143 node_id_ptr, old_node_rev_id->data,
2144 node_rev_id->data);
2145
2146 apr_hash_set(origins_hash, node_id_ptr, len, node_rev_id);
2147
2148 /* Sure, there's a race condition here. Two processes could be
2149 trying to add different cache elements to the same file at the
2150 same time, and the entries added by the first one to write will
2151 be lost. But this is just a cache of reconstructible data, so
2152 we'll accept this problem in return for not having to deal with
2153 locking overhead. */
2154
2155 /* Create a temporary file, write out our hash, and close the file. */
2156 SVN_ERR(svn_stream_open_unique(&stream, &path_tmp,
2157 svn_dirent_dirname(node_origins_path, pool),
2158 svn_io_file_del_none, pool, pool));
2159 SVN_ERR(svn_hash_write2(origins_hash, stream, SVN_HASH_TERMINATOR, pool));
2160 SVN_ERR(svn_stream_close(stream));
2161
2162 /* Rename the temp file as the real destination */
2163 return svn_io_file_rename2(path_tmp, node_origins_path, FALSE, pool);
2164 }
2165
2166
2167 svn_error_t *
svn_fs_fs__set_node_origin(svn_fs_t * fs,const svn_fs_fs__id_part_t * node_id,const svn_fs_id_t * node_rev_id,apr_pool_t * pool)2168 svn_fs_fs__set_node_origin(svn_fs_t *fs,
2169 const svn_fs_fs__id_part_t *node_id,
2170 const svn_fs_id_t *node_rev_id,
2171 apr_pool_t *pool)
2172 {
2173 svn_error_t *err;
2174 const char *filename = svn_fs_fs__path_node_origin(fs, node_id, pool);
2175
2176 err = set_node_origins_for_file(fs, filename,
2177 node_id,
2178 svn_fs_fs__id_unparse(node_rev_id, pool),
2179 pool);
2180 if (err && APR_STATUS_IS_EACCES(err->apr_err))
2181 {
2182 /* It's just a cache; stop trying if I can't write. */
2183 svn_error_clear(err);
2184 err = NULL;
2185 }
2186 return svn_error_trace(err);
2187 }
2188
2189
2190
2191 /*** Revisions ***/
2192
2193 svn_error_t *
svn_fs_fs__revision_prop(svn_string_t ** value_p,svn_fs_t * fs,svn_revnum_t rev,const char * propname,svn_boolean_t refresh,apr_pool_t * result_pool,apr_pool_t * scratch_pool)2194 svn_fs_fs__revision_prop(svn_string_t **value_p,
2195 svn_fs_t *fs,
2196 svn_revnum_t rev,
2197 const char *propname,
2198 svn_boolean_t refresh,
2199 apr_pool_t *result_pool,
2200 apr_pool_t *scratch_pool)
2201 {
2202 apr_hash_t *table;
2203
2204 SVN_ERR(svn_fs__check_fs(fs, TRUE));
2205 SVN_ERR(svn_fs_fs__get_revision_proplist(&table, fs, rev, refresh,
2206 scratch_pool, scratch_pool));
2207
2208 *value_p = svn_string_dup(svn_hash_gets(table, propname), result_pool);
2209
2210 return SVN_NO_ERROR;
2211 }
2212
2213
2214 /* Baton used for change_rev_prop_body below. */
2215 struct change_rev_prop_baton {
2216 svn_fs_t *fs;
2217 svn_revnum_t rev;
2218 const char *name;
2219 const svn_string_t *const *old_value_p;
2220 const svn_string_t *value;
2221 };
2222
2223 /* The work-horse for svn_fs_fs__change_rev_prop, called with the FS
2224 write lock. This implements the svn_fs_fs__with_write_lock()
2225 'body' callback type. BATON is a 'struct change_rev_prop_baton *'. */
2226 static svn_error_t *
change_rev_prop_body(void * baton,apr_pool_t * pool)2227 change_rev_prop_body(void *baton, apr_pool_t *pool)
2228 {
2229 struct change_rev_prop_baton *cb = baton;
2230 apr_hash_t *table;
2231 const svn_string_t *present_value;
2232
2233 /* We always need to read the current revprops from disk.
2234 * Hence, always "refresh" here. */
2235 SVN_ERR(svn_fs_fs__get_revision_proplist(&table, cb->fs, cb->rev, TRUE,
2236 pool, pool));
2237 present_value = svn_hash_gets(table, cb->name);
2238
2239 if (cb->old_value_p)
2240 {
2241 const svn_string_t *wanted_value = *cb->old_value_p;
2242 if ((!wanted_value != !present_value)
2243 || (wanted_value && present_value
2244 && !svn_string_compare(wanted_value, present_value)))
2245 {
2246 /* What we expected isn't what we found. */
2247 return svn_error_createf(SVN_ERR_FS_PROP_BASEVALUE_MISMATCH, NULL,
2248 _("revprop '%s' has unexpected value in "
2249 "filesystem"),
2250 cb->name);
2251 }
2252 /* Fall through. */
2253 }
2254
2255 /* If the prop-set is a no-op, skip the actual write. */
2256 if ((!present_value && !cb->value)
2257 || (present_value && cb->value
2258 && svn_string_compare(present_value, cb->value)))
2259 return SVN_NO_ERROR;
2260
2261 svn_hash_sets(table, cb->name, cb->value);
2262
2263 return svn_fs_fs__set_revision_proplist(cb->fs, cb->rev, table, pool);
2264 }
2265
2266 svn_error_t *
svn_fs_fs__change_rev_prop(svn_fs_t * fs,svn_revnum_t rev,const char * name,const svn_string_t * const * old_value_p,const svn_string_t * value,apr_pool_t * pool)2267 svn_fs_fs__change_rev_prop(svn_fs_t *fs,
2268 svn_revnum_t rev,
2269 const char *name,
2270 const svn_string_t *const *old_value_p,
2271 const svn_string_t *value,
2272 apr_pool_t *pool)
2273 {
2274 struct change_rev_prop_baton cb;
2275
2276 SVN_ERR(svn_fs__check_fs(fs, TRUE));
2277
2278 cb.fs = fs;
2279 cb.rev = rev;
2280 cb.name = name;
2281 cb.old_value_p = old_value_p;
2282 cb.value = value;
2283
2284 return svn_fs_fs__with_write_lock(fs, change_rev_prop_body, &cb, pool);
2285 }
2286
2287
2288 svn_error_t *
svn_fs_fs__info_format(int * fs_format,svn_version_t ** supports_version,svn_fs_t * fs,apr_pool_t * result_pool,apr_pool_t * scratch_pool)2289 svn_fs_fs__info_format(int *fs_format,
2290 svn_version_t **supports_version,
2291 svn_fs_t *fs,
2292 apr_pool_t *result_pool,
2293 apr_pool_t *scratch_pool)
2294 {
2295 fs_fs_data_t *ffd = fs->fsap_data;
2296 *fs_format = ffd->format;
2297 *supports_version = apr_palloc(result_pool, sizeof(svn_version_t));
2298
2299 (*supports_version)->major = SVN_VER_MAJOR;
2300 (*supports_version)->minor = 1;
2301 (*supports_version)->patch = 0;
2302 (*supports_version)->tag = "";
2303
2304 switch (ffd->format)
2305 {
2306 case 1:
2307 break;
2308 case 2:
2309 (*supports_version)->minor = 4;
2310 break;
2311 case 3:
2312 (*supports_version)->minor = 5;
2313 break;
2314 case 4:
2315 (*supports_version)->minor = 6;
2316 break;
2317 case 6:
2318 (*supports_version)->minor = 8;
2319 break;
2320 case 7:
2321 (*supports_version)->minor = 9;
2322 break;
2323 case 8:
2324 (*supports_version)->minor = 10;
2325 break;
2326 #ifdef SVN_DEBUG
2327 # if SVN_FS_FS__FORMAT_NUMBER != 8
2328 # error "Need to add a 'case' statement here"
2329 # endif
2330 #endif
2331 }
2332
2333 return SVN_NO_ERROR;
2334 }
2335
2336 svn_error_t *
svn_fs_fs__info_config_files(apr_array_header_t ** files,svn_fs_t * fs,apr_pool_t * result_pool,apr_pool_t * scratch_pool)2337 svn_fs_fs__info_config_files(apr_array_header_t **files,
2338 svn_fs_t *fs,
2339 apr_pool_t *result_pool,
2340 apr_pool_t *scratch_pool)
2341 {
2342 *files = apr_array_make(result_pool, 1, sizeof(const char *));
2343 APR_ARRAY_PUSH(*files, const char *) = svn_dirent_join(fs->path, PATH_CONFIG,
2344 result_pool);
2345 return SVN_NO_ERROR;
2346 }
2347
2348 static svn_error_t *
ensure_representation_sha1(svn_fs_t * fs,representation_t * rep,apr_pool_t * pool)2349 ensure_representation_sha1(svn_fs_t *fs,
2350 representation_t *rep,
2351 apr_pool_t *pool)
2352 {
2353 if (!rep->has_sha1)
2354 {
2355 svn_stream_t *contents;
2356 svn_checksum_t *checksum;
2357
2358 SVN_ERR(svn_fs_fs__get_contents(&contents, fs, rep, FALSE, pool));
2359 SVN_ERR(svn_stream_contents_checksum(&checksum, contents,
2360 svn_checksum_sha1, pool, pool));
2361
2362 memcpy(rep->sha1_digest, checksum->digest, APR_SHA1_DIGESTSIZE);
2363 rep->has_sha1 = TRUE;
2364 }
2365
2366 return SVN_NO_ERROR;
2367 }
2368
2369 static svn_error_t *
reindex_node(svn_fs_t * fs,const svn_fs_id_t * id,svn_revnum_t rev,svn_fs_fs__revision_file_t * rev_file,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * pool)2370 reindex_node(svn_fs_t *fs,
2371 const svn_fs_id_t *id,
2372 svn_revnum_t rev,
2373 svn_fs_fs__revision_file_t *rev_file,
2374 svn_cancel_func_t cancel_func,
2375 void *cancel_baton,
2376 apr_pool_t *pool)
2377 {
2378 node_revision_t *noderev;
2379 apr_off_t offset;
2380
2381 if (svn_fs_fs__id_rev(id) != rev)
2382 {
2383 return SVN_NO_ERROR;
2384 }
2385
2386 if (cancel_func)
2387 SVN_ERR(cancel_func(cancel_baton));
2388
2389 SVN_ERR(svn_fs_fs__item_offset(&offset, fs, rev_file, rev, NULL,
2390 svn_fs_fs__id_item(id), pool));
2391
2392 SVN_ERR(svn_io_file_seek(rev_file->file, APR_SET, &offset, pool));
2393 SVN_ERR(svn_fs_fs__read_noderev(&noderev, rev_file->stream,
2394 pool, pool));
2395
2396 /* Make sure EXPANDED_SIZE has the correct value for every rep. */
2397 SVN_ERR(svn_fs_fs__fixup_expanded_size(fs, noderev->data_rep, pool));
2398 SVN_ERR(svn_fs_fs__fixup_expanded_size(fs, noderev->prop_rep, pool));
2399
2400 /* First reindex sub-directory to match write_final_rev() behavior. */
2401 if (noderev->kind == svn_node_dir)
2402 {
2403 apr_array_header_t *entries;
2404
2405 SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev, pool, pool));
2406
2407 if (entries->nelts > 0)
2408 {
2409 int i;
2410 apr_pool_t *iterpool;
2411
2412 iterpool = svn_pool_create(pool);
2413 for (i = 0; i < entries->nelts; i++)
2414 {
2415 const svn_fs_dirent_t *dirent;
2416
2417 svn_pool_clear(iterpool);
2418
2419 dirent = APR_ARRAY_IDX(entries, i, svn_fs_dirent_t *);
2420
2421 SVN_ERR(reindex_node(fs, dirent->id, rev, rev_file,
2422 cancel_func, cancel_baton, iterpool));
2423 }
2424 svn_pool_destroy(iterpool);
2425 }
2426 }
2427
2428 if (noderev->data_rep && noderev->data_rep->revision == rev &&
2429 noderev->kind == svn_node_file)
2430 {
2431 SVN_ERR(ensure_representation_sha1(fs, noderev->data_rep, pool));
2432 SVN_ERR(svn_fs_fs__set_rep_reference(fs, noderev->data_rep, pool));
2433 }
2434
2435 if (noderev->prop_rep && noderev->prop_rep->revision == rev)
2436 {
2437 SVN_ERR(ensure_representation_sha1(fs, noderev->prop_rep, pool));
2438 SVN_ERR(svn_fs_fs__set_rep_reference(fs, noderev->prop_rep, pool));
2439 }
2440
2441 return SVN_NO_ERROR;
2442 }
2443
2444 svn_error_t *
svn_fs_fs__build_rep_cache(svn_fs_t * fs,svn_revnum_t start_rev,svn_revnum_t end_rev,svn_fs_progress_notify_func_t progress_func,void * progress_baton,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * pool)2445 svn_fs_fs__build_rep_cache(svn_fs_t *fs,
2446 svn_revnum_t start_rev,
2447 svn_revnum_t end_rev,
2448 svn_fs_progress_notify_func_t progress_func,
2449 void *progress_baton,
2450 svn_cancel_func_t cancel_func,
2451 void *cancel_baton,
2452 apr_pool_t *pool)
2453 {
2454 fs_fs_data_t *ffd = fs->fsap_data;
2455 apr_pool_t *iterpool;
2456 svn_revnum_t rev;
2457
2458 if (ffd->format < SVN_FS_FS__MIN_REP_SHARING_FORMAT)
2459 {
2460 return svn_error_createf(SVN_ERR_FS_REP_SHARING_NOT_SUPPORTED, NULL,
2461 _("FSFS format (%d) too old for rep-sharing; "
2462 "please upgrade the filesystem."),
2463 ffd->format);
2464 }
2465
2466 if (!ffd->rep_sharing_allowed)
2467 {
2468 return svn_error_create(SVN_ERR_FS_REP_SHARING_NOT_ALLOWED, NULL,
2469 _("Filesystem does not allow rep-sharing."));
2470 }
2471
2472 /* Do not build rep-cache for revision zero to match
2473 * svn_fs_fs__create() behavior. */
2474 if (start_rev == SVN_INVALID_REVNUM)
2475 start_rev = 1;
2476
2477 if (end_rev == SVN_INVALID_REVNUM)
2478 SVN_ERR(svn_fs_fs__youngest_rev(&end_rev, fs, pool));
2479
2480 /* Do nothing for empty FS. */
2481 if (start_rev > end_rev)
2482 {
2483 return SVN_NO_ERROR;
2484 }
2485
2486 if (!ffd->rep_cache_db)
2487 SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
2488
2489 iterpool = svn_pool_create(pool);
2490 for (rev = start_rev; rev <= end_rev; rev++)
2491 {
2492 svn_fs_id_t *root_id;
2493 svn_fs_fs__revision_file_t *file;
2494 svn_error_t *err;
2495
2496 svn_pool_clear(iterpool);
2497
2498 if (progress_func)
2499 progress_func(rev, progress_baton, iterpool);
2500
2501 SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&file, fs, rev,
2502 iterpool, iterpool));
2503 SVN_ERR(svn_fs_fs__rev_get_root(&root_id, fs, rev, iterpool, iterpool));
2504
2505 SVN_ERR(svn_sqlite__begin_transaction(ffd->rep_cache_db));
2506 err = reindex_node(fs, root_id, rev, file, cancel_func, cancel_baton, iterpool);
2507 SVN_ERR(svn_sqlite__finish_transaction(ffd->rep_cache_db, err));
2508
2509 SVN_ERR(svn_fs_fs__close_revision_file(file));
2510 }
2511
2512 svn_pool_destroy(iterpool);
2513
2514 return SVN_NO_ERROR;
2515 }
2516