/* changes.h --- FSX changed paths lists container * * ==================================================================== * 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 "svn_private_config.h" #include "svn_sorts.h" #include "private/svn_packed_data.h" #include "changes.h" #include "string_table.h" #include "temp_serializer.h" /* These flags will be used with the FLAGS field in binary_change_t. */ /* the change contains a text modification */ #define CHANGE_TEXT_MOD 0x00001 /* the change contains a property modification */ #define CHANGE_PROP_MOD 0x00002 /* the change contains a mergeinfo modification */ #define CHANGE_MERGEINFO_MOD 0x00004 /* (flags & CHANGE_NODE_MASK) >> CHANGE_NODE_SHIFT extracts the node type */ #define CHANGE_NODE_SHIFT 0x00003 #define CHANGE_NODE_MASK 0x00018 /* node types according to svn_node_kind_t */ #define CHANGE_NODE_NONE 0x00000 #define CHANGE_NODE_FILE 0x00008 #define CHANGE_NODE_DIR 0x00010 #define CHANGE_NODE_UNKNOWN 0x00018 /* (flags & CHANGE_KIND_MASK) >> CHANGE_KIND_SHIFT extracts the change type */ #define CHANGE_KIND_SHIFT 0x00005 #define CHANGE_KIND_MASK 0x00060 /* node types according to svn_fs_path_change_kind_t */ #define CHANGE_KIND_MODIFY 0x00000 #define CHANGE_KIND_ADD 0x00020 #define CHANGE_KIND_DELETE 0x00040 #define CHANGE_KIND_REPLACE 0x00060 /* Our internal representation of a change */ typedef struct binary_change_t { /* define the kind of change and what specific information is present */ int flags; /* Path of the change. */ apr_size_t path; /* copy-from information. * Not present if COPYFROM_REV is SVN_INVALID_REVNUM. */ svn_revnum_t copyfrom_rev; apr_size_t copyfrom_path; } binary_change_t; /* The actual container object. Change lists are concatenated into CHANGES * and and their begins and ends are stored in OFFSETS. */ struct svn_fs_x__changes_t { /* The paths - either in 'builder' mode or finalized mode. * The respective other pointer will be NULL. */ string_table_builder_t *builder; string_table_t *paths; /* All changes of all change lists concatenated. * Array elements are binary_change_t.structs (not pointer!) */ apr_array_header_t *changes; /* [Offsets[index] .. Offsets[index+1]) is the range in CHANGES that * forms the contents of change list INDEX. */ apr_array_header_t *offsets; }; /* Create and return a new container object, allocated in RESULT_POOL with * an initial capacity of INITIAL_COUNT changes. The PATH and BUILDER * members must be initialized by the caller afterwards. */ static svn_fs_x__changes_t * changes_create_body(apr_size_t initial_count, apr_pool_t *result_pool) { svn_fs_x__changes_t *changes = apr_pcalloc(result_pool, sizeof(*changes)); changes->changes = apr_array_make(result_pool, (int)initial_count, sizeof(binary_change_t)); changes->offsets = apr_array_make(result_pool, 16, sizeof(int)); APR_ARRAY_PUSH(changes->offsets, int) = 0; return changes; } svn_fs_x__changes_t * svn_fs_x__changes_create(apr_size_t initial_count, apr_pool_t *result_pool) { svn_fs_x__changes_t *changes = changes_create_body(initial_count, result_pool); changes->builder = svn_fs_x__string_table_builder_create(result_pool); return changes; } /* Add CHANGE to the latest change list in CHANGES. */ static svn_error_t * append_change(svn_fs_x__changes_t *changes, svn_fs_x__change_t *change) { binary_change_t binary_change = { 0 }; /* CHANGE must be sufficiently complete */ SVN_ERR_ASSERT(change); SVN_ERR_ASSERT(change->path.data); /* define the kind of change and what specific information is present */ binary_change.flags = (change->text_mod ? CHANGE_TEXT_MOD : 0) | (change->prop_mod ? CHANGE_PROP_MOD : 0) | (change->mergeinfo_mod == svn_tristate_true ? CHANGE_MERGEINFO_MOD : 0) | ((int)change->change_kind << CHANGE_KIND_SHIFT) | ((int)change->node_kind << CHANGE_NODE_SHIFT); /* Path of the change. */ binary_change.path = svn_fs_x__string_table_builder_add(changes->builder, change->path.data, change->path.len); /* copy-from information, if presence is indicated by FLAGS */ if (SVN_IS_VALID_REVNUM(change->copyfrom_rev)) { binary_change.copyfrom_rev = change->copyfrom_rev; binary_change.copyfrom_path = svn_fs_x__string_table_builder_add(changes->builder, change->copyfrom_path, 0); } else { binary_change.copyfrom_rev = SVN_INVALID_REVNUM; binary_change.copyfrom_path = 0; } APR_ARRAY_PUSH(changes->changes, binary_change_t) = binary_change; return SVN_NO_ERROR; } svn_error_t * svn_fs_x__changes_append_list(apr_size_t *list_index, svn_fs_x__changes_t *changes, apr_array_header_t *list) { int i; /* CHANGES must be in 'builder' mode */ SVN_ERR_ASSERT(changes->builder); SVN_ERR_ASSERT(changes->paths == NULL); /* simply append the list and all changes */ for (i = 0; i < list->nelts; ++i) append_change(changes, APR_ARRAY_IDX(list, i, svn_fs_x__change_t *)); /* terminate the list by storing the next changes offset */ APR_ARRAY_PUSH(changes->offsets, int) = changes->changes->nelts; *list_index = (apr_size_t)(changes->offsets->nelts - 2); return SVN_NO_ERROR; } apr_size_t svn_fs_x__changes_estimate_size(const svn_fs_x__changes_t *changes) { /* CHANGES must be in 'builder' mode */ if (changes->builder == NULL) return 0; /* string table code makes its own prediction, * changes should be < 10 bytes each, * some static overhead should be assumed */ return svn_fs_x__string_table_builder_estimate_size(changes->builder) + changes->changes->nelts * 10 + 100; } svn_error_t * svn_fs_x__changes_get_list(apr_array_header_t **list, const svn_fs_x__changes_t *changes, apr_size_t idx, svn_fs_x__changes_context_t *context, apr_pool_t *result_pool) { int list_first; int list_last; int first; int last; int i; /* CHANGES must be in 'finalized' mode */ SVN_ERR_ASSERT(changes->builder == NULL); SVN_ERR_ASSERT(changes->paths); /* validate index */ if (idx + 1 >= (apr_size_t)changes->offsets->nelts) return svn_error_createf(SVN_ERR_FS_CONTAINER_INDEX, NULL, apr_psprintf(result_pool, _("Changes list index %%%s" " exceeds container size %%d"), APR_SIZE_T_FMT), idx, changes->offsets->nelts - 1); /* range of changes to return */ list_first = APR_ARRAY_IDX(changes->offsets, (int)idx, int); list_last = APR_ARRAY_IDX(changes->offsets, (int)idx + 1, int); /* Restrict it to the sub-range requested by the caller. * Clip the range to never exceed the list's content. */ first = MIN(context->next + list_first, list_last); last = MIN(first + SVN_FS_X__CHANGES_BLOCK_SIZE, list_last); /* Indicate to the caller whether the end of the list has been reached. */ context->eol = last == list_last; /* construct result */ *list = apr_array_make(result_pool, last - first, sizeof(svn_fs_x__change_t*)); for (i = first; i < last; ++i) { const binary_change_t *binary_change = &APR_ARRAY_IDX(changes->changes, i, binary_change_t); /* convert BINARY_CHANGE into a standard FSX svn_fs_x__change_t */ svn_fs_x__change_t *change = apr_pcalloc(result_pool, sizeof(*change)); change->path.data = svn_fs_x__string_table_get(changes->paths, binary_change->path, &change->path.len, result_pool); change->change_kind = (svn_fs_path_change_kind_t) ((binary_change->flags & CHANGE_KIND_MASK) >> CHANGE_KIND_SHIFT); change->text_mod = (binary_change->flags & CHANGE_TEXT_MOD) != 0; change->prop_mod = (binary_change->flags & CHANGE_PROP_MOD) != 0; change->mergeinfo_mod = (binary_change->flags & CHANGE_MERGEINFO_MOD) ? svn_tristate_true : svn_tristate_false; change->node_kind = (svn_node_kind_t) ((binary_change->flags & CHANGE_NODE_MASK) >> CHANGE_NODE_SHIFT); change->copyfrom_rev = binary_change->copyfrom_rev; change->copyfrom_known = TRUE; if (SVN_IS_VALID_REVNUM(binary_change->copyfrom_rev)) change->copyfrom_path = svn_fs_x__string_table_get(changes->paths, binary_change->copyfrom_path, NULL, result_pool); /* add it to the result */ APR_ARRAY_PUSH(*list, svn_fs_x__change_t*) = change; } return SVN_NO_ERROR; } svn_error_t * svn_fs_x__write_changes_container(svn_stream_t *stream, const svn_fs_x__changes_t *changes, apr_pool_t *scratch_pool) { int i; string_table_t *paths = changes->paths ? changes->paths : svn_fs_x__string_table_create(changes->builder, scratch_pool); svn_packed__data_root_t *root = svn_packed__data_create_root(scratch_pool); /* one top-level stream for each array */ svn_packed__int_stream_t *offsets_stream = svn_packed__create_int_stream(root, TRUE, FALSE); svn_packed__int_stream_t *changes_stream = svn_packed__create_int_stream(root, FALSE, FALSE); /* structure the CHANGES_STREAM such we can extract much of the redundancy * from the binary_change_t structs */ svn_packed__create_int_substream(changes_stream, TRUE, FALSE); svn_packed__create_int_substream(changes_stream, TRUE, FALSE); svn_packed__create_int_substream(changes_stream, TRUE, TRUE); svn_packed__create_int_substream(changes_stream, TRUE, FALSE); /* serialize offsets array */ for (i = 0; i < changes->offsets->nelts; ++i) svn_packed__add_uint(offsets_stream, APR_ARRAY_IDX(changes->offsets, i, int)); /* serialize changes array */ for (i = 0; i < changes->changes->nelts; ++i) { const binary_change_t *change = &APR_ARRAY_IDX(changes->changes, i, binary_change_t); svn_packed__add_uint(changes_stream, change->flags); svn_packed__add_uint(changes_stream, change->path); svn_packed__add_int(changes_stream, change->copyfrom_rev); svn_packed__add_uint(changes_stream, change->copyfrom_path); } /* write to disk */ SVN_ERR(svn_fs_x__write_string_table(stream, paths, scratch_pool)); SVN_ERR(svn_packed__data_write(stream, root, scratch_pool)); return SVN_NO_ERROR; } svn_error_t * svn_fs_x__read_changes_container(svn_fs_x__changes_t **changes_p, svn_stream_t *stream, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { apr_size_t i; apr_size_t count; svn_fs_x__changes_t *changes = apr_pcalloc(result_pool, sizeof(*changes)); svn_packed__data_root_t *root; svn_packed__int_stream_t *offsets_stream; svn_packed__int_stream_t *changes_stream; /* read from disk */ SVN_ERR(svn_fs_x__read_string_table(&changes->paths, stream, result_pool, scratch_pool)); SVN_ERR(svn_packed__data_read(&root, stream, result_pool, scratch_pool)); offsets_stream = svn_packed__first_int_stream(root); changes_stream = svn_packed__next_int_stream(offsets_stream); /* read offsets array */ count = svn_packed__int_count(offsets_stream); changes->offsets = apr_array_make(result_pool, (int)count, sizeof(int)); for (i = 0; i < count; ++i) APR_ARRAY_PUSH(changes->offsets, int) = (int)svn_packed__get_uint(offsets_stream); /* read changes array */ count = svn_packed__int_count(svn_packed__first_int_substream(changes_stream)); changes->changes = apr_array_make(result_pool, (int)count, sizeof(binary_change_t)); for (i = 0; i < count; ++i) { binary_change_t change; change.flags = (int)svn_packed__get_uint(changes_stream); change.path = (apr_size_t)svn_packed__get_uint(changes_stream); change.copyfrom_rev = (svn_revnum_t)svn_packed__get_int(changes_stream); change.copyfrom_path = (apr_size_t)svn_packed__get_uint(changes_stream); APR_ARRAY_PUSH(changes->changes, binary_change_t) = change; } *changes_p = changes; return SVN_NO_ERROR; } svn_error_t * svn_fs_x__serialize_changes_container(void **data, apr_size_t *data_len, void *in, apr_pool_t *pool) { svn_fs_x__changes_t *changes = in; svn_stringbuf_t *serialized; /* make a guesstimate on the size of the serialized data. Erring on the * low side will cause the serializer to re-alloc its buffer. */ apr_size_t size = changes->changes->elt_size * changes->changes->nelts + changes->offsets->elt_size * changes->offsets->nelts + 10 * changes->changes->elt_size + 100; /* serialize array header and all its elements */ svn_temp_serializer__context_t *context = svn_temp_serializer__init(changes, sizeof(*changes), size, pool); /* serialize sub-structures */ svn_fs_x__serialize_string_table(context, &changes->paths); svn_fs_x__serialize_apr_array(context, &changes->changes); svn_fs_x__serialize_apr_array(context, &changes->offsets); /* return the serialized result */ serialized = svn_temp_serializer__get(context); *data = serialized->data; *data_len = serialized->len; return SVN_NO_ERROR; } svn_error_t * svn_fs_x__deserialize_changes_container(void **out, void *data, apr_size_t data_len, apr_pool_t *result_pool) { svn_fs_x__changes_t *changes = (svn_fs_x__changes_t *)data; /* de-serialize sub-structures */ svn_fs_x__deserialize_string_table(changes, &changes->paths); svn_fs_x__deserialize_apr_array(changes, &changes->changes, result_pool); svn_fs_x__deserialize_apr_array(changes, &changes->offsets, result_pool); /* done */ *out = changes; return SVN_NO_ERROR; } svn_error_t * svn_fs_x__changes_get_list_func(void **out, const void *data, apr_size_t data_len, void *baton, apr_pool_t *pool) { int first; int last; int i; apr_array_header_t *list; svn_fs_x__changes_get_list_baton_t *b = baton; apr_uint32_t idx = b->sub_item; const svn_fs_x__changes_t *container = data; /* resolve all the sub-container pointers we need */ const string_table_t *paths = svn_temp_deserializer__ptr(container, (const void *const *)&container->paths); const apr_array_header_t *serialized_offsets = svn_temp_deserializer__ptr(container, (const void *const *)&container->offsets); const apr_array_header_t *serialized_changes = svn_temp_deserializer__ptr(container, (const void *const *)&container->changes); const int *offsets = svn_temp_deserializer__ptr(serialized_offsets, (const void *const *)&serialized_offsets->elts); const binary_change_t *changes = svn_temp_deserializer__ptr(serialized_changes, (const void *const *)&serialized_changes->elts); /* validate index */ if (idx + 1 >= (apr_size_t)serialized_offsets->nelts) return svn_error_createf(SVN_ERR_FS_CONTAINER_INDEX, NULL, _("Changes list index %u exceeds container " "size %d"), (unsigned)idx, serialized_offsets->nelts - 1); /* range of changes to return */ first = offsets[idx]; last = offsets[idx+1]; /* Restrict range to the block requested by the BATON. * Tell the caller whether we reached the end of the list. */ first = MIN(first + b->start, last); last = MIN(first + SVN_FS_X__CHANGES_BLOCK_SIZE, last); *b->eol = last == offsets[idx+1]; /* construct result */ list = apr_array_make(pool, last - first, sizeof(svn_fs_x__change_t*)); for (i = first; i < last; ++i) { const binary_change_t *binary_change = &changes[i]; /* convert BINARY_CHANGE into a standard FSX svn_fs_x__change_t */ svn_fs_x__change_t *change = apr_pcalloc(pool, sizeof(*change)); change->path.data = svn_fs_x__string_table_get_func(paths, binary_change->path, &change->path.len, pool); change->change_kind = (svn_fs_path_change_kind_t) ((binary_change->flags & CHANGE_KIND_MASK) >> CHANGE_KIND_SHIFT); change->text_mod = (binary_change->flags & CHANGE_TEXT_MOD) != 0; change->prop_mod = (binary_change->flags & CHANGE_PROP_MOD) != 0; change->node_kind = (svn_node_kind_t) ((binary_change->flags & CHANGE_NODE_MASK) >> CHANGE_NODE_SHIFT); change->copyfrom_rev = binary_change->copyfrom_rev; change->copyfrom_known = TRUE; if (SVN_IS_VALID_REVNUM(binary_change->copyfrom_rev)) change->copyfrom_path = svn_fs_x__string_table_get_func(paths, binary_change->copyfrom_path, NULL, pool); /* add it to the result */ APR_ARRAY_PUSH(list, svn_fs_x__change_t*) = change; } *out = list; return SVN_NO_ERROR; }