| /* revprops.c --- everything needed to handle revprops in FSFS |
| * |
| * ==================================================================== |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * ==================================================================== |
| */ |
| |
| #include <assert.h> |
| |
| #include "svn_pools.h" |
| #include "svn_hash.h" |
| #include "svn_dirent_uri.h" |
| #include "svn_sorts.h" |
| |
| #include "fs_fs.h" |
| #include "revprops.h" |
| #include "temp_serializer.h" |
| #include "util.h" |
| |
| #include "private/svn_subr_private.h" |
| #include "private/svn_string_private.h" |
| #include "../libsvn_fs/fs-loader.h" |
| |
| #include "svn_private_config.h" |
| |
| svn_error_t * |
| svn_fs_fs__upgrade_pack_revprops(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 *scratch_pool) |
| { |
| fs_fs_data_t *ffd = fs->fsap_data; |
| const char *revprops_shard_path; |
| const char *revprops_pack_file_dir; |
| apr_int64_t shard; |
| apr_int64_t first_unpacked_shard |
| = ffd->min_unpacked_rev / ffd->max_files_per_dir; |
| |
| apr_pool_t *iterpool = svn_pool_create(scratch_pool); |
| const char *revsprops_dir = svn_dirent_join(fs->path, PATH_REVPROPS_DIR, |
| scratch_pool); |
| int compression_level = ffd->compress_packed_revprops |
| ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT |
| : SVN_DELTA_COMPRESSION_LEVEL_NONE; |
| |
| /* first, pack all revprops shards to match the packed revision shards */ |
| for (shard = 0; shard < first_unpacked_shard; ++shard) |
| { |
| svn_pool_clear(iterpool); |
| |
| revprops_pack_file_dir = svn_dirent_join(revsprops_dir, |
| apr_psprintf(iterpool, |
| "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD, |
| shard), |
| iterpool); |
| revprops_shard_path = svn_dirent_join(revsprops_dir, |
| apr_psprintf(iterpool, "%" APR_INT64_T_FMT, shard), |
| iterpool); |
| |
| SVN_ERR(svn_fs_fs__pack_revprops_shard(revprops_pack_file_dir, |
| revprops_shard_path, |
| shard, ffd->max_files_per_dir, |
| (int)(0.9 * ffd->revprop_pack_size), |
| compression_level, |
| ffd->flush_to_disk, |
| cancel_func, cancel_baton, |
| iterpool)); |
| if (notify_func) |
| SVN_ERR(notify_func(notify_baton, shard, |
| svn_fs_upgrade_pack_revprops, iterpool)); |
| } |
| |
| svn_pool_destroy(iterpool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * |
| svn_fs_fs__upgrade_cleanup_pack_revprops(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 *scratch_pool) |
| { |
| fs_fs_data_t *ffd = fs->fsap_data; |
| const char *revprops_shard_path; |
| apr_int64_t shard; |
| apr_int64_t first_unpacked_shard |
| = ffd->min_unpacked_rev / ffd->max_files_per_dir; |
| |
| apr_pool_t *iterpool = svn_pool_create(scratch_pool); |
| const char *revsprops_dir = svn_dirent_join(fs->path, PATH_REVPROPS_DIR, |
| scratch_pool); |
| |
| /* delete the non-packed revprops shards afterwards */ |
| for (shard = 0; shard < first_unpacked_shard; ++shard) |
| { |
| svn_pool_clear(iterpool); |
| |
| revprops_shard_path = svn_dirent_join(revsprops_dir, |
| apr_psprintf(iterpool, "%" APR_INT64_T_FMT, shard), |
| iterpool); |
| SVN_ERR(svn_fs_fs__delete_revprops_shard(revprops_shard_path, |
| shard, |
| ffd->max_files_per_dir, |
| cancel_func, cancel_baton, |
| iterpool)); |
| if (notify_func) |
| SVN_ERR(notify_func(notify_baton, shard, |
| svn_fs_upgrade_cleanup_revprops, iterpool)); |
| } |
| |
| svn_pool_destroy(iterpool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Container for all data required to access the packed revprop file |
| * for a given REVISION. This structure will be filled incrementally |
| * by read_pack_revprops() its sub-routines. |
| */ |
| typedef struct packed_revprops_t |
| { |
| /* revision number to read (not necessarily the first in the pack) */ |
| svn_revnum_t revision; |
| |
| /* the actual revision properties */ |
| apr_hash_t *properties; |
| |
| /* their size when serialized to a single string |
| * (as found in PACKED_REVPROPS) */ |
| apr_size_t serialized_size; |
| |
| |
| /* name of the pack file (without folder path) */ |
| const char *filename; |
| |
| /* packed shard folder path */ |
| const char *folder; |
| |
| /* sum of values in SIZES */ |
| apr_size_t total_size; |
| |
| /* first revision in the pack (>= MANIFEST_START) */ |
| svn_revnum_t start_revision; |
| |
| /* size of the revprops in PACKED_REVPROPS */ |
| apr_array_header_t *sizes; |
| |
| /* offset of the revprops in PACKED_REVPROPS */ |
| apr_array_header_t *offsets; |
| |
| |
| /* concatenation of the serialized representation of all revprops |
| * in the pack, i.e. the pack content without header and compression */ |
| svn_stringbuf_t *packed_revprops; |
| |
| /* First revision covered by MANIFEST. |
| * Will equal the shard start revision or 1, for the 1st shard. */ |
| svn_revnum_t manifest_start; |
| |
| /* content of the manifest. |
| * Maps long(rev - MANIFEST_START) to const char* pack file name */ |
| apr_array_header_t *manifest; |
| } packed_revprops_t; |
| |
| /* Parse the serialized revprops in CONTENT and return them in *PROPERTIES. |
| * Also, put them into the revprop cache, if activated, for future use. |
| * |
| * The returned hash will be allocated in RESULT_POOL, SCRATCH_POOL is being |
| * used for temporary allocations. |
| */ |
| static svn_error_t * |
| parse_revprop(apr_hash_t **properties, |
| svn_fs_t *fs, |
| svn_revnum_t revision, |
| svn_string_t *content, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| svn_stream_t *stream = svn_stream_from_string(content, scratch_pool); |
| *properties = apr_hash_make(result_pool); |
| |
| SVN_ERR_W(svn_hash_read2(*properties, stream, SVN_HASH_TERMINATOR, |
| result_pool), |
| apr_psprintf(scratch_pool, "Failed to parse revprops for r%ld.", |
| revision)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| void |
| svn_fs_fs__reset_revprop_cache(svn_fs_t *fs) |
| { |
| fs_fs_data_t *ffd = fs->fsap_data; |
| ffd->revprop_prefix = 0; |
| } |
| |
| /* If FS has not a revprop cache prefix set, generate one. |
| * Always call this before accessing the revprop cache. |
| */ |
| static svn_error_t * |
| prepare_revprop_cache(svn_fs_t *fs, |
| apr_pool_t *scratch_pool) |
| { |
| fs_fs_data_t *ffd = fs->fsap_data; |
| if (!ffd->revprop_prefix) |
| SVN_ERR(svn_atomic__unique_counter(&ffd->revprop_prefix)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Store the unparsed revprop hash CONTENT for REVISION in FS's revprop |
| * cache. If CACHED is not NULL, set *CACHED if there already is such |
| * an entry and skip the cache write in that case. Use SCRATCH_POOL for |
| * temporary allocations. */ |
| static svn_error_t * |
| cache_revprops(svn_boolean_t *is_cached, |
| svn_fs_t *fs, |
| svn_revnum_t revision, |
| svn_string_t *content, |
| apr_pool_t *scratch_pool) |
| { |
| fs_fs_data_t *ffd = fs->fsap_data; |
| pair_cache_key_t key; |
| |
| /* Make sure prepare_revprop_cache() has been called. */ |
| SVN_ERR_ASSERT(ffd->revprop_prefix); |
| key.revision = revision; |
| key.second = ffd->revprop_prefix; |
| |
| if (is_cached) |
| { |
| SVN_ERR(svn_cache__has_key(is_cached, ffd->revprop_cache, &key, |
| scratch_pool)); |
| if (*is_cached) |
| return SVN_NO_ERROR; |
| } |
| |
| SVN_ERR(svn_cache__set(ffd->revprop_cache, &key, content, scratch_pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Read the non-packed revprops for revision REV in FS, put them into the |
| * revprop cache if PROPULATE_CACHE is set and return them in *PROPERTIES. |
| * |
| * If the data could not be read due to an otherwise recoverable error, |
| * leave *PROPERTIES unchanged. No error will be returned in that case. |
| * |
| * Allocations will be done in POOL. |
| */ |
| static svn_error_t * |
| read_non_packed_revprop(apr_hash_t **properties, |
| svn_fs_t *fs, |
| svn_revnum_t rev, |
| svn_boolean_t populate_cache, |
| apr_pool_t *pool) |
| { |
| svn_stringbuf_t *content = NULL; |
| apr_pool_t *iterpool = svn_pool_create(pool); |
| svn_boolean_t missing = FALSE; |
| int i; |
| |
| for (i = 0; |
| i < SVN_FS_FS__RECOVERABLE_RETRY_COUNT && !missing && !content; |
| ++i) |
| { |
| svn_pool_clear(iterpool); |
| SVN_ERR(svn_fs_fs__try_stringbuf_from_file(&content, |
| &missing, |
| svn_fs_fs__path_revprops(fs, rev, iterpool), |
| i + 1 < SVN_FS_FS__RECOVERABLE_RETRY_COUNT , |
| iterpool)); |
| } |
| |
| if (content) |
| { |
| svn_string_t *as_string = svn_stringbuf__morph_into_string(content); |
| SVN_ERR(parse_revprop(properties, fs, rev, as_string, pool, iterpool)); |
| |
| if (populate_cache) |
| SVN_ERR(cache_revprops(NULL, fs, rev, as_string, iterpool)); |
| } |
| |
| svn_pool_clear(iterpool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Return the minimum length of any packed revprop file name in REVPROPS. */ |
| static apr_size_t |
| get_min_filename_len(packed_revprops_t *revprops) |
| { |
| char number_buffer[SVN_INT64_BUFFER_SIZE]; |
| |
| /* The revprop filenames have the format <REV>.<COUNT> - with <REV> being |
| * at least the first rev in the shard and <COUNT> having at least one |
| * digit. Thus, the minimum is 2 + #decimal places in the start rev. |
| */ |
| return svn__i64toa(number_buffer, revprops->manifest_start) + 2; |
| } |
| |
| /* Given FS and REVPROPS->REVISION, fill the FILENAME, FOLDER and MANIFEST |
| * members. Use RESULT_POOL for allocating results and SCRATCH_POOL for |
| * temporaries. |
| */ |
| static svn_error_t * |
| get_revprop_packname(svn_fs_t *fs, |
| packed_revprops_t *revprops, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| fs_fs_data_t *ffd = fs->fsap_data; |
| svn_stringbuf_t *content = NULL; |
| const char *manifest_file_path; |
| int idx, rev_count; |
| char *buffer, *buffer_end; |
| const char **filenames, **filenames_end; |
| apr_size_t min_filename_len; |
| |
| /* Determine the dimensions. Rev 0 is excluded from the first shard. */ |
| rev_count = ffd->max_files_per_dir; |
| revprops->manifest_start |
| = revprops->revision - (revprops->revision % rev_count); |
| if (revprops->manifest_start == 0) |
| { |
| ++revprops->manifest_start; |
| --rev_count; |
| } |
| |
| revprops->manifest = apr_array_make(result_pool, rev_count, |
| sizeof(const char*)); |
| |
| /* No line in the file can be less than this number of chars long. */ |
| min_filename_len = get_min_filename_len(revprops); |
| |
| /* Read the content of the manifest file */ |
| revprops->folder |
| = svn_fs_fs__path_revprops_pack_shard(fs, revprops->revision, |
| result_pool); |
| manifest_file_path |
| = svn_dirent_join(revprops->folder, PATH_MANIFEST, result_pool); |
| |
| SVN_ERR(svn_fs_fs__read_content(&content, manifest_file_path, result_pool)); |
| |
| /* There CONTENT must have a certain minimal size and there no |
| * unterminated lines at the end of the file. Both guarantees also |
| * simplify the parser loop below. |
| */ |
| if ( content->len < rev_count * (min_filename_len + 1) |
| || content->data[content->len - 1] != '\n') |
| return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, |
| _("Packed revprop manifest for r%ld not " |
| "properly terminated"), revprops->revision); |
| |
| /* Chop (parse) the manifest CONTENT into filenames, one per line. |
| * We only have to replace all newlines with NUL and add all line |
| * starts to REVPROPS->MANIFEST. |
| * |
| * There must be exactly REV_COUNT lines and that is the number of |
| * lines we parse from BUFFER to FILENAMES. Set the end pointer for |
| * the source BUFFER such that BUFFER+MIN_FILENAME_LEN is still valid |
| * BUFFER_END is always valid due to CONTENT->LEN > MIN_FILENAME_LEN. |
| * |
| * Please note that this loop is performance critical for e.g. 'svn log'. |
| * It is run 1000x per revprop access, i.e. per revision and about |
| * 50 million times per sec (and CPU core). |
| */ |
| for (filenames = (const char **)revprops->manifest->elts, |
| filenames_end = filenames + rev_count, |
| buffer = content->data, |
| buffer_end = buffer + content->len - min_filename_len; |
| (filenames < filenames_end) && (buffer < buffer_end); |
| ++filenames) |
| { |
| /* BUFFER always points to the start of the next line / filename. */ |
| *filenames = buffer; |
| |
| /* Find the next EOL. This is guaranteed to stay within the CONTENT |
| * buffer because we left enough room after BUFFER_END and we know |
| * we will always see a newline as the last non-NUL char. */ |
| buffer += min_filename_len; |
| while (*buffer != '\n') |
| ++buffer; |
| |
| /* Found EOL. Turn it into the filename terminator and move BUFFER |
| * to the start of the next line or CONTENT buffer end. */ |
| *buffer = '\0'; |
| ++buffer; |
| } |
| |
| /* We must have reached the end of both buffers. */ |
| if (buffer < content->data + content->len) |
| return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, |
| _("Packed revprop manifest for r%ld " |
| "has too many entries"), revprops->revision); |
| |
| if (filenames < filenames_end) |
| return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, |
| _("Packed revprop manifest for r%ld " |
| "has too few entries"), revprops->revision); |
| |
| /* The target array has now exactly one entry per revision. */ |
| revprops->manifest->nelts = rev_count; |
| |
| /* Now get the file name */ |
| idx = (int)(revprops->revision - revprops->manifest_start); |
| revprops->filename = APR_ARRAY_IDX(revprops->manifest, idx, const char*); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Return TRUE, if revision R1 and R2 refer to the same shard in FS. |
| */ |
| static svn_boolean_t |
| same_shard(svn_fs_t *fs, |
| svn_revnum_t r1, |
| svn_revnum_t r2) |
| { |
| fs_fs_data_t *ffd = fs->fsap_data; |
| return (r1 / ffd->max_files_per_dir) == (r2 / ffd->max_files_per_dir); |
| } |
| |
| /* Given FS and the full packed file content in REVPROPS->PACKED_REVPROPS, |
| * fill the START_REVISION member, and make PACKED_REVPROPS point to the |
| * first serialized revprop. If READ_ALL is set, initialize the SIZES |
| * and OFFSETS members as well. If POPULATE_CACHE is set, cache all |
| * revprops found in this pack. |
| * |
| * Parse the revprops for REVPROPS->REVISION and set the PROPERTIES as |
| * well as the SERIALIZED_SIZE member. If revprop caching has been |
| * enabled, parse all revprops in the pack and cache them. |
| */ |
| static svn_error_t * |
| parse_packed_revprops(svn_fs_t *fs, |
| packed_revprops_t *revprops, |
| svn_boolean_t read_all, |
| svn_boolean_t populate_cache, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| svn_stream_t *stream; |
| apr_int64_t first_rev, count, i; |
| apr_size_t offset; |
| const char *header_end; |
| apr_pool_t *iterpool = svn_pool_create(scratch_pool); |
| |
| /* Initial value for the "Leaking bucket" pattern. */ |
| int bucket = 4; |
| |
| /* decompress (even if the data is only "stored", there is still a |
| * length header to remove) */ |
| svn_stringbuf_t *compressed = revprops->packed_revprops; |
| svn_stringbuf_t *uncompressed = svn_stringbuf_create_empty(result_pool); |
| SVN_ERR(svn__decompress_zlib(compressed->data, compressed->len, |
| uncompressed, APR_SIZE_MAX)); |
| |
| /* read first revision number and number of revisions in the pack */ |
| stream = svn_stream_from_stringbuf(uncompressed, scratch_pool); |
| SVN_ERR(svn_fs_fs__read_number_from_stream(&first_rev, NULL, stream, |
| iterpool)); |
| SVN_ERR(svn_fs_fs__read_number_from_stream(&count, NULL, stream, |
| iterpool)); |
| |
| /* Check revision range for validity. */ |
| if ( !same_shard(fs, revprops->revision, first_rev) |
| || !same_shard(fs, revprops->revision, first_rev + count - 1) |
| || count < 1) |
| return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, |
| _("Revprop pack for revision r%ld" |
| " contains revprops for r%ld .. r%ld"), |
| revprops->revision, |
| (svn_revnum_t)first_rev, |
| (svn_revnum_t)(first_rev + count -1)); |
| |
| /* Since start & end are in the same shard, it is enough to just test |
| * the FIRST_REV for being actually packed. That will also cover the |
| * special case of rev 0 never being packed. */ |
| if (!svn_fs_fs__is_packed_revprop(fs, first_rev)) |
| return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, |
| _("Revprop pack for revision r%ld" |
| " starts at non-packed revisions r%ld"), |
| revprops->revision, (svn_revnum_t)first_rev); |
| |
| /* make PACKED_REVPROPS point to the first char after the header. |
| * This is where the serialized revprops are. */ |
| header_end = strstr(uncompressed->data, "\n\n"); |
| if (header_end == NULL) |
| return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, |
| _("Header end not found")); |
| |
| offset = header_end - uncompressed->data + 2; |
| |
| revprops->packed_revprops = svn_stringbuf_create_empty(result_pool); |
| revprops->packed_revprops->data = uncompressed->data + offset; |
| revprops->packed_revprops->len = (apr_size_t)(uncompressed->len - offset); |
| revprops->packed_revprops->blocksize = (apr_size_t)(uncompressed->blocksize |
| - offset); |
| |
| /* STREAM still points to the first entry in the sizes list. */ |
| revprops->start_revision = (svn_revnum_t)first_rev; |
| if (read_all) |
| { |
| /* Init / construct REVPROPS members. */ |
| revprops->sizes = apr_array_make(result_pool, (int)count, |
| sizeof(offset)); |
| revprops->offsets = apr_array_make(result_pool, (int)count, |
| sizeof(offset)); |
| } |
| |
| /* Now parse, revision by revision, the size and content of each |
| * revisions' revprops. */ |
| for (i = 0, offset = 0, revprops->total_size = 0; i < count; ++i) |
| { |
| apr_int64_t size; |
| svn_string_t serialized; |
| svn_revnum_t revision = (svn_revnum_t)(first_rev + i); |
| svn_pool_clear(iterpool); |
| |
| /* read & check the serialized size */ |
| SVN_ERR(svn_fs_fs__read_number_from_stream(&size, NULL, stream, |
| iterpool)); |
| if (size > (apr_int64_t)revprops->packed_revprops->len - offset) |
| return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, |
| _("Packed revprop size exceeds pack file size")); |
| |
| /* Parse this revprops list, if necessary */ |
| serialized.data = revprops->packed_revprops->data + offset; |
| serialized.len = (apr_size_t)size; |
| |
| if (revision == revprops->revision) |
| { |
| /* Parse (and possibly cache) the one revprop list we care about. */ |
| SVN_ERR(parse_revprop(&revprops->properties, fs, revision, |
| &serialized, result_pool, iterpool)); |
| revprops->serialized_size = serialized.len; |
| |
| /* If we only wanted the revprops for REVISION then we are done. */ |
| if (!read_all && !populate_cache) |
| break; |
| } |
| |
| if (populate_cache) |
| { |
| /* Adding all those revprops is expensive, in particular in a |
| * multi-threaded environment. There are situations where hit |
| * rates are low and revprops get evicted before re-using them. |
| * |
| * We try to detect thosse cases here. |
| * Only keep going while most (at least 2/3) aren't cached, yet. */ |
| svn_boolean_t already_cached; |
| SVN_ERR(cache_revprops(&already_cached, fs, revision, &serialized, |
| iterpool)); |
| |
| /* Stop populating the cache once we encountered too many entries |
| * already present relative to the numbers being added. */ |
| if (!already_cached) |
| { |
| ++bucket; |
| } |
| else |
| { |
| bucket -= 2; |
| if (bucket < 0) |
| populate_cache = FALSE; |
| } |
| } |
| |
| if (read_all) |
| { |
| /* fill REVPROPS data structures */ |
| APR_ARRAY_PUSH(revprops->sizes, apr_size_t) = serialized.len; |
| APR_ARRAY_PUSH(revprops->offsets, apr_size_t) = offset; |
| } |
| revprops->total_size += serialized.len; |
| |
| offset += serialized.len; |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* In filesystem FS, read the packed revprops for revision REV into |
| * *REVPROPS. Populate the revprop cache, if POPULATE_CACHE is set. |
| * If you want to modify revprop contents / update REVPROPS, READ_ALL |
| * must be set. Otherwise, only the properties of REV are being provided. |
| * Allocate data in POOL. |
| */ |
| static svn_error_t * |
| read_pack_revprop(packed_revprops_t **revprops, |
| svn_fs_t *fs, |
| svn_revnum_t rev, |
| svn_boolean_t read_all, |
| svn_boolean_t populate_cache, |
| apr_pool_t *pool) |
| { |
| apr_pool_t *iterpool = svn_pool_create(pool); |
| svn_boolean_t missing = FALSE; |
| svn_error_t *err; |
| packed_revprops_t *result; |
| int i; |
| |
| /* someone insisted that REV is packed. Double-check if necessary */ |
| if (!svn_fs_fs__is_packed_revprop(fs, rev)) |
| SVN_ERR(svn_fs_fs__update_min_unpacked_rev(fs, iterpool)); |
| |
| if (!svn_fs_fs__is_packed_revprop(fs, rev)) |
| return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, |
| _("No such packed revision %ld"), rev); |
| |
| /* initialize the result data structure */ |
| result = apr_pcalloc(pool, sizeof(*result)); |
| result->revision = rev; |
| |
| /* try to read the packed revprops. This may require retries if we have |
| * concurrent writers. */ |
| for (i = 0; |
| i < SVN_FS_FS__RECOVERABLE_RETRY_COUNT && !result->packed_revprops; |
| ++i) |
| { |
| const char *file_path; |
| svn_pool_clear(iterpool); |
| |
| /* there might have been concurrent writes. |
| * Re-read the manifest and the pack file. |
| */ |
| SVN_ERR(get_revprop_packname(fs, result, pool, iterpool)); |
| file_path = svn_dirent_join(result->folder, |
| result->filename, |
| iterpool); |
| SVN_ERR(svn_fs_fs__try_stringbuf_from_file(&result->packed_revprops, |
| &missing, |
| file_path, |
| i + 1 < SVN_FS_FS__RECOVERABLE_RETRY_COUNT, |
| pool)); |
| } |
| |
| /* the file content should be available now */ |
| if (!result->packed_revprops) |
| return svn_error_createf(SVN_ERR_FS_PACKED_REVPROP_READ_FAILURE, NULL, |
| _("Failed to read revprop pack file for r%ld"), rev); |
| |
| /* parse it. RESULT will be complete afterwards. */ |
| err = parse_packed_revprops(fs, result, read_all, populate_cache, pool, |
| iterpool); |
| svn_pool_destroy(iterpool); |
| if (err) |
| return svn_error_createf(SVN_ERR_FS_CORRUPT, err, |
| _("Revprop pack file for r%ld is corrupt"), rev); |
| |
| *revprops = result; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * |
| svn_fs_fs__get_revision_props_size(apr_off_t *props_size_p, |
| svn_fs_t *fs, |
| svn_revnum_t rev, |
| apr_pool_t *scratch_pool) |
| { |
| fs_fs_data_t *ffd = fs->fsap_data; |
| |
| /* should they be available at all? */ |
| SVN_ERR(svn_fs_fs__ensure_revision_exists(rev, fs, scratch_pool)); |
| |
| /* if REV had not been packed when we began, try reading it from the |
| * non-packed shard. If that fails, we will fall through to packed |
| * shard reads. */ |
| if (!svn_fs_fs__is_packed_revprop(fs, rev)) |
| { |
| const char *path = svn_fs_fs__path_revprops(fs, rev, scratch_pool); |
| svn_error_t *err; |
| apr_file_t *file; |
| svn_filesize_t file_size; |
| |
| err = svn_io_file_open(&file, path, APR_FOPEN_READ, APR_OS_DEFAULT, |
| scratch_pool); |
| if (!err) |
| err = svn_io_file_size_get(&file_size, file, scratch_pool); |
| if (!err) |
| { |
| *props_size_p = (apr_off_t)file_size; |
| return SVN_NO_ERROR; |
| } |
| else if (!APR_STATUS_IS_ENOENT(err->apr_err) |
| || ffd->format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT) |
| { |
| return svn_error_trace(err); |
| } |
| |
| /* fall through: maybe the revision got packed while we were looking */ |
| svn_error_clear(err); |
| } |
| |
| /* Try reading packed revprops. If that fails, REV is most |
| * likely invalid (or its revprops highly contested). */ |
| { |
| packed_revprops_t *revprops; |
| |
| /* ### This is inefficient -- reading all the revprops in a pack. We |
| should just read the index. */ |
| SVN_ERR(read_pack_revprop(&revprops, fs, rev, |
| TRUE /*read_all*/, FALSE /*populate_cache*/, |
| scratch_pool)); |
| *props_size_p = (apr_off_t)APR_ARRAY_IDX(revprops->sizes, |
| rev - revprops->start_revision, |
| apr_size_t); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Read the revprops for revision REV in FS and return them in *PROPERTIES_P. |
| * |
| * Allocations will be done in POOL. |
| */ |
| svn_error_t * |
| svn_fs_fs__get_revision_proplist(apr_hash_t **proplist_p, |
| svn_fs_t *fs, |
| svn_revnum_t rev, |
| svn_boolean_t refresh, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| fs_fs_data_t *ffd = fs->fsap_data; |
| |
| /* Only populate the cache if we did not just cross a sync barrier. |
| * This is to eliminate overhead from code that always sets REFRESH. |
| * For callers that want caching, the caching kicks in on read "later". */ |
| svn_boolean_t populate_cache = !refresh; |
| |
| /* not found, yet */ |
| *proplist_p = NULL; |
| |
| /* should they be available at all? */ |
| SVN_ERR(svn_fs_fs__ensure_revision_exists(rev, fs, scratch_pool)); |
| |
| if (refresh) |
| { |
| /* Previous cache contents is invalid now. */ |
| svn_fs_fs__reset_revprop_cache(fs); |
| } |
| else |
| { |
| /* Try cache lookup first. */ |
| svn_boolean_t is_cached; |
| pair_cache_key_t key; |
| |
| /* Auto-alloc prefix and construct the key. */ |
| SVN_ERR(prepare_revprop_cache(fs, scratch_pool)); |
| key.revision = rev; |
| key.second = ffd->revprop_prefix; |
| |
| /* The only way that this might error out is due to parser error. */ |
| SVN_ERR_W(svn_cache__get((void **) proplist_p, &is_cached, |
| ffd->revprop_cache, &key, result_pool), |
| apr_psprintf(scratch_pool, |
| "Failed to parse revprops for r%ld.", |
| rev)); |
| if (is_cached) |
| return SVN_NO_ERROR; |
| } |
| |
| /* if REV had not been packed when we began, try reading it from the |
| * non-packed shard. If that fails, we will fall through to packed |
| * shard reads. */ |
| if (!svn_fs_fs__is_packed_revprop(fs, rev)) |
| { |
| svn_error_t *err = read_non_packed_revprop(proplist_p, fs, rev, |
| populate_cache, result_pool); |
| if (err) |
| { |
| if (!APR_STATUS_IS_ENOENT(err->apr_err) |
| || ffd->format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT) |
| return svn_error_trace(err); |
| |
| svn_error_clear(err); |
| *proplist_p = NULL; /* in case read_non_packed_revprop changed it */ |
| } |
| } |
| |
| /* if revprop packing is available and we have not read the revprops, yet, |
| * try reading them from a packed shard. If that fails, REV is most |
| * likely invalid (or its revprops highly contested). */ |
| if (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT && !*proplist_p) |
| { |
| packed_revprops_t *revprops; |
| SVN_ERR(read_pack_revprop(&revprops, fs, rev, FALSE, populate_cache, |
| result_pool)); |
| *proplist_p = revprops->properties; |
| } |
| |
| /* The revprops should have been there. Did we get them? */ |
| if (!*proplist_p) |
| return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, |
| _("Could not read revprops for revision %ld"), |
| rev); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Serialize the revision property list PROPLIST of revision REV in |
| * filesystem FS to a non-packed file. Return the name of that temporary |
| * file in *TMP_PATH and the file path that it must be moved to in |
| * *FINAL_PATH. |
| * |
| * Use POOL for allocations. |
| */ |
| static svn_error_t * |
| write_non_packed_revprop(const char **final_path, |
| const char **tmp_path, |
| svn_fs_t *fs, |
| svn_revnum_t rev, |
| apr_hash_t *proplist, |
| apr_pool_t *pool) |
| { |
| fs_fs_data_t *ffd = fs->fsap_data; |
| apr_file_t *file; |
| svn_stream_t *stream; |
| *final_path = svn_fs_fs__path_revprops(fs, rev, pool); |
| |
| /* ### do we have a directory sitting around already? we really shouldn't |
| ### have to get the dirname here. */ |
| SVN_ERR(svn_io_open_unique_file3(&file, tmp_path, |
| svn_dirent_dirname(*final_path, pool), |
| svn_io_file_del_none, pool, pool)); |
| stream = svn_stream_from_aprfile2(file, TRUE, pool); |
| SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, pool)); |
| SVN_ERR(svn_stream_close(stream)); |
| |
| /* Flush temporary file to disk and close it. */ |
| if (ffd->flush_to_disk) |
| SVN_ERR(svn_io_file_flush_to_disk(file, pool)); |
| SVN_ERR(svn_io_file_close(file, pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* After writing the new revprop file(s), call this function to move the |
| * file at TMP_PATH to FINAL_PATH and give it the permissions from |
| * PERMS_REFERENCE. |
| * |
| * Finally, delete all the temporary files given in FILES_TO_DELETE. |
| * The latter may be NULL. |
| * |
| * Use POOL for temporary allocations. |
| */ |
| static svn_error_t * |
| switch_to_new_revprop(svn_fs_t *fs, |
| const char *final_path, |
| const char *tmp_path, |
| const char *perms_reference, |
| apr_array_header_t *files_to_delete, |
| apr_pool_t *pool) |
| { |
| fs_fs_data_t *ffd = fs->fsap_data; |
| |
| SVN_ERR(svn_fs_fs__move_into_place(tmp_path, final_path, perms_reference, |
| ffd->flush_to_disk, pool)); |
| |
| /* Clean up temporary files, if necessary. */ |
| if (files_to_delete) |
| { |
| apr_pool_t *iterpool = svn_pool_create(pool); |
| int i; |
| |
| for (i = 0; i < files_to_delete->nelts; ++i) |
| { |
| const char *path = APR_ARRAY_IDX(files_to_delete, i, const char*); |
| |
| svn_pool_clear(iterpool); |
| SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool)); |
| } |
| |
| svn_pool_destroy(iterpool); |
| } |
| return SVN_NO_ERROR; |
| } |
| |
| /* Write a pack file header to STREAM that starts at revision START_REVISION |
| * and contains the indexes [START,END) of SIZES. |
| */ |
| static svn_error_t * |
| serialize_revprops_header(svn_stream_t *stream, |
| svn_revnum_t start_revision, |
| apr_array_header_t *sizes, |
| int start, |
| int end, |
| apr_pool_t *pool) |
| { |
| apr_pool_t *iterpool = svn_pool_create(pool); |
| int i; |
| |
| SVN_ERR_ASSERT(start < end); |
| |
| /* start revision and entry count */ |
| SVN_ERR(svn_stream_printf(stream, pool, "%ld\n", start_revision)); |
| SVN_ERR(svn_stream_printf(stream, pool, "%d\n", end - start)); |
| |
| /* the sizes array */ |
| for (i = start; i < end; ++i) |
| { |
| /* Non-standard pool usage. |
| * |
| * We only allocate a few bytes each iteration -- even with a |
| * million iterations we would still be in good shape memory-wise. |
| */ |
| apr_size_t size = APR_ARRAY_IDX(sizes, i, apr_size_t); |
| SVN_ERR(svn_stream_printf(stream, iterpool, "%" APR_SIZE_T_FMT "\n", |
| size)); |
| } |
| |
| /* the double newline char indicates the end of the header */ |
| SVN_ERR(svn_stream_puts(stream, "\n")); |
| |
| svn_pool_destroy(iterpool); |
| return SVN_NO_ERROR; |
| } |
| |
| /* Writes the a pack file to FILE. It copies the serialized data |
| * from REVPROPS for the indexes [START,END) except for index CHANGED_INDEX. |
| * |
| * The data for the latter is taken from NEW_SERIALIZED. Note, that |
| * CHANGED_INDEX may be outside the [START,END) range, i.e. no new data is |
| * taken in that case but only a subset of the old data will be copied. |
| * |
| * NEW_TOTAL_SIZE is a hint for pre-allocating buffers of appropriate size. |
| * POOL is used for temporary allocations. |
| */ |
| static svn_error_t * |
| repack_revprops(svn_fs_t *fs, |
| packed_revprops_t *revprops, |
| int start, |
| int end, |
| int changed_index, |
| svn_stringbuf_t *new_serialized, |
| apr_size_t new_total_size, |
| apr_file_t *file, |
| apr_pool_t *pool) |
| { |
| fs_fs_data_t *ffd = fs->fsap_data; |
| svn_stream_t *stream; |
| int i; |
| |
| /* create data empty buffers and the stream object */ |
| svn_stringbuf_t *uncompressed |
| = svn_stringbuf_create_ensure((apr_size_t)new_total_size, pool); |
| svn_stringbuf_t *compressed |
| = svn_stringbuf_create_empty(pool); |
| stream = svn_stream_from_stringbuf(uncompressed, pool); |
| |
| /* write the header*/ |
| SVN_ERR(serialize_revprops_header(stream, revprops->start_revision + start, |
| revprops->sizes, start, end, pool)); |
| |
| /* append the serialized revprops */ |
| for (i = start; i < end; ++i) |
| if (i == changed_index) |
| { |
| SVN_ERR(svn_stream_write(stream, |
| new_serialized->data, |
| &new_serialized->len)); |
| } |
| else |
| { |
| apr_size_t size = APR_ARRAY_IDX(revprops->sizes, i, apr_size_t); |
| apr_size_t offset = APR_ARRAY_IDX(revprops->offsets, i, apr_size_t); |
| |
| SVN_ERR(svn_stream_write(stream, |
| revprops->packed_revprops->data + offset, |
| &size)); |
| } |
| |
| /* flush the stream buffer (if any) to our underlying data buffer */ |
| SVN_ERR(svn_stream_close(stream)); |
| |
| /* compress / store the data */ |
| SVN_ERR(svn__compress_zlib(uncompressed->data, uncompressed->len, |
| compressed, |
| ffd->compress_packed_revprops |
| ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT |
| : SVN_DELTA_COMPRESSION_LEVEL_NONE)); |
| |
| /* finally, write the content to the target file, flush and close it */ |
| SVN_ERR(svn_io_file_write_full(file, compressed->data, compressed->len, |
| NULL, pool)); |
| if (ffd->flush_to_disk) |
| SVN_ERR(svn_io_file_flush_to_disk(file, pool)); |
| SVN_ERR(svn_io_file_close(file, pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Allocate a new pack file name for revisions |
| * [REVPROPS->START_REVISION + START, REVPROPS->START_REVISION + END - 1] |
| * of REVPROPS->MANIFEST. Add the name of old file to FILES_TO_DELETE, |
| * auto-create that array if necessary. Return an open file *FILE that is |
| * allocated in POOL. |
| */ |
| static svn_error_t * |
| repack_file_open(apr_file_t **file, |
| svn_fs_t *fs, |
| packed_revprops_t *revprops, |
| int start, |
| int end, |
| apr_array_header_t **files_to_delete, |
| apr_pool_t *pool) |
| { |
| apr_int64_t tag; |
| const char *tag_string; |
| const char *new_filename; |
| int i; |
| int manifest_offset |
| = (int)(revprops->start_revision - revprops->manifest_start); |
| |
| /* get the old (= current) file name and enlist it for later deletion */ |
| const char *old_filename = APR_ARRAY_IDX(revprops->manifest, |
| start + manifest_offset, |
| const char*); |
| |
| if (*files_to_delete == NULL) |
| *files_to_delete = apr_array_make(pool, 3, sizeof(const char*)); |
| |
| APR_ARRAY_PUSH(*files_to_delete, const char*) |
| = svn_dirent_join(revprops->folder, old_filename, pool); |
| |
| /* increase the tag part, i.e. the counter after the dot */ |
| tag_string = strchr(old_filename, '.'); |
| if (tag_string == NULL) |
| return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, |
| _("Packed file '%s' misses a tag"), |
| old_filename); |
| |
| SVN_ERR(svn_cstring_atoi64(&tag, tag_string + 1)); |
| new_filename = apr_psprintf(pool, "%ld.%" APR_INT64_T_FMT, |
| revprops->start_revision + start, |
| ++tag); |
| |
| /* update the manifest to point to the new file */ |
| for (i = start; i < end; ++i) |
| APR_ARRAY_IDX(revprops->manifest, i + manifest_offset, const char*) |
| = new_filename; |
| |
| /* open the file */ |
| SVN_ERR(svn_io_file_open(file, svn_dirent_join(revprops->folder, |
| new_filename, |
| pool), |
| APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* For revision REV in filesystem FS, set the revision properties to |
| * PROPLIST. Return a new file in *TMP_PATH that the caller shall move |
| * to *FINAL_PATH to make the change visible. Files to be deleted will |
| * be listed in *FILES_TO_DELETE which may remain unchanged / unallocated. |
| * Use POOL for allocations. |
| */ |
| static svn_error_t * |
| write_packed_revprop(const char **final_path, |
| const char **tmp_path, |
| apr_array_header_t **files_to_delete, |
| svn_fs_t *fs, |
| svn_revnum_t rev, |
| apr_hash_t *proplist, |
| apr_pool_t *pool) |
| { |
| fs_fs_data_t *ffd = fs->fsap_data; |
| packed_revprops_t *revprops; |
| svn_stream_t *stream; |
| apr_file_t *file; |
| svn_stringbuf_t *serialized; |
| apr_size_t new_total_size; |
| int changed_index; |
| |
| /* read contents of the current pack file */ |
| SVN_ERR(read_pack_revprop(&revprops, fs, rev, TRUE, FALSE, pool)); |
| |
| /* serialize the new revprops */ |
| serialized = svn_stringbuf_create_empty(pool); |
| stream = svn_stream_from_stringbuf(serialized, pool); |
| SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, pool)); |
| SVN_ERR(svn_stream_close(stream)); |
| |
| /* calculate the size of the new data */ |
| changed_index = (int)(rev - revprops->start_revision); |
| new_total_size = revprops->total_size - revprops->serialized_size |
| + serialized->len |
| + (revprops->offsets->nelts + 2) * SVN_INT64_BUFFER_SIZE; |
| |
| APR_ARRAY_IDX(revprops->sizes, changed_index, apr_size_t) = serialized->len; |
| |
| /* can we put the new data into the same pack as the before? */ |
| if ( new_total_size < ffd->revprop_pack_size |
| || revprops->sizes->nelts == 1) |
| { |
| /* simply replace the old pack file with new content as we do it |
| * in the non-packed case */ |
| |
| *final_path = svn_dirent_join(revprops->folder, revprops->filename, |
| pool); |
| SVN_ERR(svn_io_open_unique_file3(&file, tmp_path, revprops->folder, |
| svn_io_file_del_none, pool, pool)); |
| SVN_ERR(repack_revprops(fs, revprops, 0, revprops->sizes->nelts, |
| changed_index, serialized, new_total_size, |
| file, pool)); |
| } |
| else |
| { |
| /* split the pack file into two of roughly equal size */ |
| int right_count, left_count, i; |
| |
| int left = 0; |
| int right = revprops->sizes->nelts - 1; |
| apr_size_t left_size = 2 * SVN_INT64_BUFFER_SIZE; |
| apr_size_t right_size = 2 * SVN_INT64_BUFFER_SIZE; |
| |
| /* let left and right side grow such that their size difference |
| * is minimal after each step. */ |
| while (left <= right) |
| if ( left_size + APR_ARRAY_IDX(revprops->sizes, left, apr_size_t) |
| < right_size + APR_ARRAY_IDX(revprops->sizes, right, apr_size_t)) |
| { |
| left_size += APR_ARRAY_IDX(revprops->sizes, left, apr_size_t) |
| + SVN_INT64_BUFFER_SIZE; |
| ++left; |
| } |
| else |
| { |
| right_size += APR_ARRAY_IDX(revprops->sizes, right, apr_size_t) |
| + SVN_INT64_BUFFER_SIZE; |
| --right; |
| } |
| |
| /* since the items need much less than SVN_INT64_BUFFER_SIZE |
| * bytes to represent their length, the split may not be optimal */ |
| left_count = left; |
| right_count = revprops->sizes->nelts - left; |
| |
| /* if new_size is large, one side may exceed the pack size limit. |
| * In that case, split before and after the modified revprop.*/ |
| if ( left_size > ffd->revprop_pack_size |
| || right_size > ffd->revprop_pack_size) |
| { |
| left_count = changed_index; |
| right_count = revprops->sizes->nelts - left_count - 1; |
| } |
| |
| /* write the new, split files */ |
| if (left_count) |
| { |
| SVN_ERR(repack_file_open(&file, fs, revprops, 0, |
| left_count, files_to_delete, pool)); |
| SVN_ERR(repack_revprops(fs, revprops, 0, left_count, |
| changed_index, serialized, new_total_size, |
| file, pool)); |
| } |
| |
| if (left_count + right_count < revprops->sizes->nelts) |
| { |
| SVN_ERR(repack_file_open(&file, fs, revprops, changed_index, |
| changed_index + 1, files_to_delete, |
| pool)); |
| SVN_ERR(repack_revprops(fs, revprops, changed_index, |
| changed_index + 1, |
| changed_index, serialized, new_total_size, |
| file, pool)); |
| } |
| |
| if (right_count) |
| { |
| SVN_ERR(repack_file_open(&file, fs, revprops, |
| revprops->sizes->nelts - right_count, |
| revprops->sizes->nelts, |
| files_to_delete, pool)); |
| SVN_ERR(repack_revprops(fs, revprops, |
| revprops->sizes->nelts - right_count, |
| revprops->sizes->nelts, changed_index, |
| serialized, new_total_size, file, |
| pool)); |
| } |
| |
| /* write the new manifest */ |
| *final_path = svn_dirent_join(revprops->folder, PATH_MANIFEST, pool); |
| SVN_ERR(svn_io_open_unique_file3(&file, tmp_path, revprops->folder, |
| svn_io_file_del_none, pool, pool)); |
| stream = svn_stream_from_aprfile2(file, TRUE, pool); |
| for (i = 0; i < revprops->manifest->nelts; ++i) |
| { |
| const char *filename = APR_ARRAY_IDX(revprops->manifest, i, |
| const char*); |
| SVN_ERR(svn_stream_printf(stream, pool, "%s\n", filename)); |
| } |
| SVN_ERR(svn_stream_close(stream)); |
| if (ffd->flush_to_disk) |
| SVN_ERR(svn_io_file_flush_to_disk(file, pool)); |
| SVN_ERR(svn_io_file_close(file, pool)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Set the revision property list of revision REV in filesystem FS to |
| PROPLIST. Use POOL for temporary allocations. */ |
| svn_error_t * |
| svn_fs_fs__set_revision_proplist(svn_fs_t *fs, |
| svn_revnum_t rev, |
| apr_hash_t *proplist, |
| apr_pool_t *pool) |
| { |
| svn_boolean_t is_packed; |
| const char *final_path; |
| const char *tmp_path; |
| const char *perms_reference; |
| apr_array_header_t *files_to_delete = NULL; |
| |
| SVN_ERR(svn_fs_fs__ensure_revision_exists(rev, fs, pool)); |
| |
| /* this info will not change while we hold the global FS write lock */ |
| is_packed = svn_fs_fs__is_packed_revprop(fs, rev); |
| |
| /* Serialize the new revprop data */ |
| if (is_packed) |
| SVN_ERR(write_packed_revprop(&final_path, &tmp_path, &files_to_delete, |
| fs, rev, proplist, pool)); |
| else |
| SVN_ERR(write_non_packed_revprop(&final_path, &tmp_path, |
| fs, rev, proplist, pool)); |
| |
| /* Previous cache contents is invalid now. */ |
| svn_fs_fs__reset_revprop_cache(fs); |
| |
| /* We use the rev file of this revision as the perms reference, |
| * because when setting revprops for the first time, the revprop |
| * file won't exist and therefore can't serve as its own reference. |
| * (Whereas the rev file should already exist at this point.) |
| */ |
| perms_reference = svn_fs_fs__path_rev_absolute(fs, rev, pool); |
| |
| /* Now, switch to the new revprop data. */ |
| SVN_ERR(switch_to_new_revprop(fs, final_path, tmp_path, perms_reference, |
| files_to_delete, pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Return TRUE, if for REVISION in FS, we can find the revprop pack file. |
| * Use POOL for temporary allocations. |
| * Set *MISSING, if the reason is a missing manifest or pack file. |
| */ |
| svn_boolean_t |
| svn_fs_fs__packed_revprop_available(svn_boolean_t *missing, |
| svn_fs_t *fs, |
| svn_revnum_t revision, |
| apr_pool_t *pool) |
| { |
| fs_fs_data_t *ffd = fs->fsap_data; |
| svn_stringbuf_t *content = NULL; |
| |
| /* try to read the manifest file */ |
| const char *folder |
| = svn_fs_fs__path_revprops_pack_shard(fs, revision, pool); |
| const char *manifest_path = svn_dirent_join(folder, PATH_MANIFEST, pool); |
| |
| svn_error_t *err = svn_fs_fs__try_stringbuf_from_file(&content, |
| missing, |
| manifest_path, |
| FALSE, |
| pool); |
| |
| /* if the manifest cannot be read, consider the pack files inaccessible |
| * even if the file itself exists. */ |
| if (err) |
| { |
| svn_error_clear(err); |
| return FALSE; |
| } |
| |
| if (*missing) |
| return FALSE; |
| |
| /* parse manifest content until we find the entry for REVISION. |
| * Revision 0 is never packed. */ |
| revision = revision < ffd->max_files_per_dir |
| ? revision - 1 |
| : revision % ffd->max_files_per_dir; |
| while (content->data) |
| { |
| char *next = strchr(content->data, '\n'); |
| if (next) |
| { |
| *next = 0; |
| ++next; |
| } |
| |
| if (revision-- == 0) |
| { |
| /* the respective pack file must exist (and be a file) */ |
| svn_node_kind_t kind; |
| err = svn_io_check_path(svn_dirent_join(folder, content->data, |
| pool), |
| &kind, pool); |
| if (err) |
| { |
| svn_error_clear(err); |
| return FALSE; |
| } |
| |
| *missing = kind == svn_node_none; |
| return kind == svn_node_file; |
| } |
| |
| content->data = next; |
| } |
| |
| return FALSE; |
| } |
| |
| |
| /****** Packing FSFS shards *********/ |
| |
| svn_error_t * |
| svn_fs_fs__copy_revprops(const char *pack_file_dir, |
| const char *pack_filename, |
| const char *shard_path, |
| svn_revnum_t start_rev, |
| svn_revnum_t end_rev, |
| apr_array_header_t *sizes, |
| apr_size_t total_size, |
| int compression_level, |
| svn_boolean_t flush_to_disk, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| apr_pool_t *scratch_pool) |
| { |
| svn_stream_t *pack_stream; |
| apr_file_t *pack_file; |
| svn_revnum_t rev; |
| apr_pool_t *iterpool = svn_pool_create(scratch_pool); |
| |
| /* create empty data buffer and a write stream on top of it */ |
| svn_stringbuf_t *uncompressed |
| = svn_stringbuf_create_ensure(total_size, scratch_pool); |
| svn_stringbuf_t *compressed |
| = svn_stringbuf_create_empty(scratch_pool); |
| pack_stream = svn_stream_from_stringbuf(uncompressed, scratch_pool); |
| |
| /* write the pack file header */ |
| SVN_ERR(serialize_revprops_header(pack_stream, start_rev, sizes, 0, |
| sizes->nelts, iterpool)); |
| |
| /* Some useful paths. */ |
| SVN_ERR(svn_io_file_open(&pack_file, svn_dirent_join(pack_file_dir, |
| pack_filename, |
| scratch_pool), |
| APR_WRITE | APR_CREATE, APR_OS_DEFAULT, |
| scratch_pool)); |
| |
| /* Iterate over the revisions in this shard, squashing them together. */ |
| for (rev = start_rev; rev <= end_rev; rev++) |
| { |
| const char *path; |
| svn_stream_t *stream; |
| apr_file_t *file; |
| |
| svn_pool_clear(iterpool); |
| |
| /* Construct the file name. */ |
| path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev), |
| iterpool); |
| |
| /* Copy all the bits from the non-packed revprop file to the end of |
| * the pack file. Use unbuffered apr_file_t since we're going to |
| * write using 16kb chunks. */ |
| SVN_ERR(svn_io_file_open(&file, path, APR_READ, APR_OS_DEFAULT, |
| iterpool)); |
| stream = svn_stream_from_aprfile2(file, FALSE, iterpool); |
| SVN_ERR(svn_stream_copy3(stream, pack_stream, |
| cancel_func, cancel_baton, iterpool)); |
| } |
| |
| /* flush stream buffers to content buffer */ |
| SVN_ERR(svn_stream_close(pack_stream)); |
| |
| /* compress the content (or just store it for COMPRESSION_LEVEL 0) */ |
| SVN_ERR(svn__compress_zlib(uncompressed->data, uncompressed->len, |
| compressed, compression_level)); |
| |
| /* write the pack file content to disk */ |
| SVN_ERR(svn_io_file_write_full(pack_file, compressed->data, compressed->len, |
| NULL, scratch_pool)); |
| if (flush_to_disk) |
| SVN_ERR(svn_io_file_flush_to_disk(pack_file, scratch_pool)); |
| SVN_ERR(svn_io_file_close(pack_file, scratch_pool)); |
| |
| svn_pool_destroy(iterpool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * |
| svn_fs_fs__pack_revprops_shard(const char *pack_file_dir, |
| const char *shard_path, |
| apr_int64_t shard, |
| int max_files_per_dir, |
| apr_int64_t max_pack_size, |
| int compression_level, |
| svn_boolean_t flush_to_disk, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| apr_pool_t *scratch_pool) |
| { |
| const char *manifest_file_path, *pack_filename = NULL; |
| apr_file_t *manifest_file; |
| svn_stream_t *manifest_stream; |
| svn_revnum_t start_rev, end_rev, rev; |
| apr_size_t total_size; |
| apr_pool_t *iterpool = svn_pool_create(scratch_pool); |
| apr_array_header_t *sizes; |
| |
| /* Sanitize config file values. */ |
| apr_size_t max_size = (apr_size_t)MIN(MAX(max_pack_size, 1), |
| SVN_MAX_OBJECT_SIZE); |
| |
| /* Some useful paths. */ |
| manifest_file_path = svn_dirent_join(pack_file_dir, PATH_MANIFEST, |
| scratch_pool); |
| |
| /* Remove any existing pack file for this shard, since it is incomplete. */ |
| SVN_ERR(svn_io_remove_dir2(pack_file_dir, TRUE, cancel_func, cancel_baton, |
| scratch_pool)); |
| |
| /* Create the new directory and manifest file stream. */ |
| SVN_ERR(svn_io_dir_make(pack_file_dir, APR_OS_DEFAULT, scratch_pool)); |
| |
| SVN_ERR(svn_io_file_open(&manifest_file, manifest_file_path, |
| APR_WRITE | APR_BUFFERED | APR_CREATE | APR_EXCL, |
| APR_OS_DEFAULT, scratch_pool)); |
| manifest_stream = svn_stream_from_aprfile2(manifest_file, TRUE, |
| scratch_pool); |
| |
| /* revisions to handle. Special case: revision 0 */ |
| start_rev = (svn_revnum_t) (shard * max_files_per_dir); |
| end_rev = (svn_revnum_t) ((shard + 1) * (max_files_per_dir) - 1); |
| if (start_rev == 0) |
| ++start_rev; |
| /* Special special case: if max_files_per_dir is 1, then at this point |
| start_rev == 1 and end_rev == 0 (!). Fortunately, everything just |
| works. */ |
| |
| /* initialize the revprop size info */ |
| sizes = apr_array_make(scratch_pool, max_files_per_dir, sizeof(apr_size_t)); |
| total_size = 2 * SVN_INT64_BUFFER_SIZE; |
| |
| /* Iterate over the revisions in this shard, determine their size and |
| * squashing them together into pack files. */ |
| for (rev = start_rev; rev <= end_rev; rev++) |
| { |
| apr_finfo_t finfo; |
| const char *path; |
| |
| svn_pool_clear(iterpool); |
| |
| /* Get the size of the file. */ |
| path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev), |
| iterpool); |
| SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_SIZE, iterpool)); |
| |
| /* If we already have started a pack file and this revprop cannot be |
| * appended to it, write the previous pack file. Note this overflow |
| * check works because we enforced MAX_SIZE <= SVN_MAX_OBJECT_SIZE. */ |
| if (sizes->nelts != 0 |
| && ( finfo.size > max_size |
| || total_size > max_size |
| || SVN_INT64_BUFFER_SIZE + finfo.size > max_size - total_size)) |
| { |
| SVN_ERR(svn_fs_fs__copy_revprops(pack_file_dir, pack_filename, |
| shard_path, start_rev, rev-1, |
| sizes, total_size, |
| compression_level, flush_to_disk, |
| cancel_func, cancel_baton, |
| iterpool)); |
| |
| /* next pack file starts empty again */ |
| apr_array_clear(sizes); |
| total_size = 2 * SVN_INT64_BUFFER_SIZE; |
| start_rev = rev; |
| } |
| |
| /* Update the manifest. Allocate a file name for the current pack |
| * file if it is a new one */ |
| if (sizes->nelts == 0) |
| pack_filename = apr_psprintf(scratch_pool, "%ld.0", rev); |
| |
| SVN_ERR(svn_stream_printf(manifest_stream, iterpool, "%s\n", |
| pack_filename)); |
| |
| /* add to list of files to put into the current pack file */ |
| APR_ARRAY_PUSH(sizes, apr_size_t) = finfo.size; |
| total_size += SVN_INT64_BUFFER_SIZE + finfo.size; |
| } |
| |
| /* write the last pack file */ |
| if (sizes->nelts != 0) |
| SVN_ERR(svn_fs_fs__copy_revprops(pack_file_dir, pack_filename, |
| shard_path, start_rev, rev-1, |
| sizes, (apr_size_t)total_size, |
| compression_level, flush_to_disk, |
| cancel_func, cancel_baton, iterpool)); |
| |
| /* flush the manifest file to disk and update permissions */ |
| SVN_ERR(svn_stream_close(manifest_stream)); |
| if (flush_to_disk) |
| SVN_ERR(svn_io_file_flush_to_disk(manifest_file, iterpool)); |
| SVN_ERR(svn_io_file_close(manifest_file, iterpool)); |
| SVN_ERR(svn_io_copy_perms(shard_path, pack_file_dir, iterpool)); |
| |
| svn_pool_destroy(iterpool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * |
| svn_fs_fs__delete_revprops_shard(const char *shard_path, |
| apr_int64_t shard, |
| int max_files_per_dir, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| apr_pool_t *scratch_pool) |
| { |
| if (shard == 0) |
| { |
| apr_pool_t *iterpool = svn_pool_create(scratch_pool); |
| int i; |
| |
| /* delete all files except the one for revision 0 */ |
| for (i = 1; i < max_files_per_dir; ++i) |
| { |
| const char *path; |
| svn_pool_clear(iterpool); |
| |
| path = svn_dirent_join(shard_path, |
| apr_psprintf(iterpool, "%d", i), |
| iterpool); |
| if (cancel_func) |
| SVN_ERR(cancel_func(cancel_baton)); |
| |
| SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool)); |
| } |
| |
| svn_pool_destroy(iterpool); |
| } |
| else |
| SVN_ERR(svn_io_remove_dir2(shard_path, TRUE, |
| cancel_func, cancel_baton, scratch_pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |