1 /* string_table.c : operations on string tables
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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
20 * ====================================================================
25 #include <apr_tables.h>
27 #include "svn_string.h"
28 #include "svn_sorts.h"
29 #include "private/svn_dep_compat.h"
30 #include "private/svn_string_private.h"
31 #include "private/svn_subr_private.h"
32 #include "private/svn_packed_data.h"
33 #include "string_table.h"
37 #define MAX_DATA_SIZE 0xffff
38 #define MAX_SHORT_STRING_LEN (MAX_DATA_SIZE / 4)
39 #define TABLE_SHIFT 13
40 #define MAX_STRINGS_PER_TABLE (1 << (TABLE_SHIFT - 1))
41 #define LONG_STRING_MASK (1 << (TABLE_SHIFT - 1))
42 #define STRING_INDEX_MASK ((1 << (TABLE_SHIFT - 1)) - 1)
43 #define PADDING (sizeof(apr_uint64_t))
46 typedef struct builder_string_t
51 struct builder_string_t *previous;
52 struct builder_string_t *next;
53 apr_size_t previous_match_len;
54 apr_size_t next_match_len;
55 struct builder_string_t *left;
56 struct builder_string_t *right;
59 typedef struct builder_table_t
61 apr_size_t max_data_size;
62 builder_string_t *top;
63 builder_string_t *first;
64 builder_string_t *last;
65 apr_array_header_t *short_strings;
66 apr_array_header_t *long_strings;
67 apr_hash_t *long_string_dict;
68 apr_size_t long_string_size;
71 struct string_table_builder_t
74 apr_array_header_t *tables;
77 typedef struct string_header_t
79 apr_uint16_t head_string;
80 apr_uint16_t head_length;
81 apr_uint16_t tail_start;
82 apr_uint16_t tail_length;
85 typedef struct string_sub_table_t
90 string_header_t *short_strings;
91 apr_size_t short_string_count;
93 svn_string_t *long_strings;
94 apr_size_t long_string_count;
100 string_sub_table_t *sub_tables;
104 /* Accessing ID Pieces. */
106 static builder_table_t *
107 add_table(string_table_builder_t *builder)
109 builder_table_t *table = apr_pcalloc(builder->pool, sizeof(*table));
110 table->max_data_size = MAX_DATA_SIZE - PADDING; /* ensure there remain a few
111 unused bytes at the end */
112 table->short_strings = apr_array_make(builder->pool, 64,
113 sizeof(builder_string_t *));
114 table->long_strings = apr_array_make(builder->pool, 0,
115 sizeof(svn_string_t));
116 table->long_string_dict = svn_hash__make(builder->pool);
118 APR_ARRAY_PUSH(builder->tables, builder_table_t *) = table;
123 string_table_builder_t *
124 svn_fs_x__string_table_builder_create(apr_pool_t *result_pool)
126 string_table_builder_t *result = apr_palloc(result_pool, sizeof(*result));
127 result->pool = result_pool;
128 result->tables = apr_array_make(result_pool, 1, sizeof(builder_table_t *));
136 balance(builder_table_t *table,
137 builder_string_t **parent,
138 builder_string_t *node)
140 apr_size_t left_height = node->left ? node->left->depth + 1 : 0;
141 apr_size_t right_height = node->right ? node->right->depth + 1 : 0;
143 if (left_height > right_height + 1)
145 builder_string_t *temp = node->left->right;
146 node->left->right = node;
147 *parent = node->left;
152 else if (left_height + 1 < right_height)
154 builder_string_t *temp = node->right->left;
155 *parent = node->right;
156 node->right->left = node;
162 node->depth = MAX(left_height, right_height);
166 match_length(const svn_string_t *lhs,
167 const svn_string_t *rhs)
169 apr_size_t len = MIN(lhs->len, rhs->len);
170 return (apr_uint16_t)svn_cstring__match_length(lhs->data, rhs->data, len);
174 insert_string(builder_table_t *table,
175 builder_string_t **parent,
176 builder_string_t *to_insert)
179 builder_string_t *current = *parent;
180 int diff = strcmp(current->string.data, to_insert->string.data);
183 apr_array_pop(table->short_strings);
184 return current->position;
189 if (current->left == NULL)
191 current->left = to_insert;
193 to_insert->previous = current->previous;
194 to_insert->next = current;
196 if (to_insert->previous == NULL)
198 table->first = to_insert;
202 builder_string_t *previous = to_insert->previous;
203 to_insert->previous_match_len
204 = match_length(&previous->string, &to_insert->string);
206 previous->next = to_insert;
207 previous->next_match_len = to_insert->previous_match_len;
210 current->previous = to_insert;
211 to_insert->next_match_len
212 = match_length(¤t->string, &to_insert->string);
213 current->previous_match_len = to_insert->next_match_len;
215 table->max_data_size -= to_insert->string.len;
216 if (to_insert->previous == NULL)
217 table->max_data_size += to_insert->next_match_len;
219 table->max_data_size += MIN(to_insert->previous_match_len,
220 to_insert->next_match_len);
222 return to_insert->position;
225 result = insert_string(table, ¤t->left, to_insert);
229 if (current->right == NULL)
231 current->right = to_insert;
233 to_insert->next = current->next;
234 to_insert->previous = current;
236 if (to_insert->next == NULL)
238 table->last = to_insert;
242 builder_string_t *next = to_insert->next;
243 to_insert->next_match_len
244 = match_length(&next->string, &to_insert->string);
246 next->previous = to_insert;
247 next->previous_match_len = to_insert->next_match_len;
250 current->next = current->right;
251 to_insert->previous_match_len
252 = match_length(¤t->string, &to_insert->string);
253 current->next_match_len = to_insert->previous_match_len;
255 table->max_data_size -= to_insert->string.len;
256 if (to_insert->next == NULL)
257 table->max_data_size += to_insert->previous_match_len;
259 table->max_data_size += MIN(to_insert->previous_match_len,
260 to_insert->next_match_len);
262 return to_insert->position;
265 result = insert_string(table, ¤t->right, to_insert);
268 balance(table, parent, current);
273 svn_fs_x__string_table_builder_add(string_table_builder_t *builder,
278 builder_table_t *table = APR_ARRAY_IDX(builder->tables,
279 builder->tables->nelts - 1,
282 len = strlen(string);
284 string = apr_pstrmemdup(builder->pool, string, len);
285 if (len > MAX_SHORT_STRING_LEN)
292 idx_void = apr_hash_get(table->long_string_dict, string, len);
293 result = (apr_uintptr_t)idx_void;
297 + (((apr_size_t)builder->tables->nelts - 1) << TABLE_SHIFT);
299 if (table->long_strings->nelts == MAX_STRINGS_PER_TABLE)
300 table = add_table(builder);
302 result = table->long_strings->nelts
304 + (((apr_size_t)builder->tables->nelts - 1) << TABLE_SHIFT);
305 APR_ARRAY_PUSH(table->long_strings, svn_string_t) = item;
306 apr_hash_set(table->long_string_dict, string, len,
307 (void*)(apr_uintptr_t)table->long_strings->nelts);
309 table->long_string_size += len;
313 builder_string_t *item = apr_pcalloc(builder->pool, sizeof(*item));
314 item->string.data = string;
315 item->string.len = len;
316 item->previous_match_len = 0;
317 item->next_match_len = 0;
319 if ( table->short_strings->nelts == MAX_STRINGS_PER_TABLE
320 || table->max_data_size < len)
321 table = add_table(builder);
323 item->position = table->short_strings->nelts;
324 APR_ARRAY_PUSH(table->short_strings, builder_string_t *) = item;
326 if (table->top == NULL)
328 table->max_data_size -= len;
333 result = ((apr_size_t)builder->tables->nelts - 1) << TABLE_SHIFT;
337 result = insert_string(table, &table->top, item)
338 + (((apr_size_t)builder->tables->nelts - 1) << TABLE_SHIFT);
346 svn_fs_x__string_table_builder_estimate_size(string_table_builder_t *builder)
348 apr_size_t total = 0;
351 for (i = 0; i < builder->tables->nelts; ++i)
353 builder_table_t *table
354 = APR_ARRAY_IDX(builder->tables, i, builder_table_t*);
356 /* total number of chars to store,
357 * 8 bytes per short string table entry
358 * 4 bytes per long string table entry
359 * some static overhead */
360 apr_size_t table_size
361 = MAX_DATA_SIZE - table->max_data_size
362 + table->long_string_size
363 + table->short_strings->nelts * 8
364 + table->long_strings->nelts * 4
370 /* ZIP compression should give us a 50% reduction.
371 * add some static overhead */
372 return 200 + total / 2;
377 create_table(string_sub_table_t *target,
378 builder_table_t *source,
380 apr_pool_t *scratch_pool)
383 apr_hash_t *tails = svn_hash__make(scratch_pool);
384 svn_stringbuf_t *data
385 = svn_stringbuf_create_ensure(MAX_DATA_SIZE - source->max_data_size,
388 /* pack sub-strings */
389 target->short_string_count = (apr_size_t)source->short_strings->nelts;
390 target->short_strings = apr_palloc(pool, sizeof(*target->short_strings) *
391 target->short_string_count);
392 for (i = 0; i < source->short_strings->nelts; ++i)
394 const builder_string_t *string
395 = APR_ARRAY_IDX(source->short_strings, i, const builder_string_t *);
397 string_header_t *entry = &target->short_strings[i];
398 const char *tail = string->string.data + string->previous_match_len;
399 string_header_t *tail_match;
400 apr_size_t head_length = string->previous_match_len;
402 /* Minimize the number of strings to visit when reconstructing the
403 string head. So, skip all predecessors that don't contribute to
404 first HEAD_LENGTH chars of our string. */
407 const builder_string_t *furthest_prev = string->previous;
408 while (furthest_prev->previous_match_len >= head_length)
409 furthest_prev = furthest_prev->previous;
410 entry->head_string = furthest_prev->position;
413 entry->head_string = 0;
415 /* head & tail length are known */
416 entry->head_length = (apr_uint16_t)head_length;
418 = (apr_uint16_t)(string->string.len - entry->head_length);
420 /* try to reuse an existing tail segment */
421 tail_match = apr_hash_get(tails, tail, entry->tail_length);
424 entry->tail_start = tail_match->tail_start;
428 entry->tail_start = (apr_uint16_t)data->len;
429 svn_stringbuf_appendbytes(data, tail, entry->tail_length);
430 apr_hash_set(tails, tail, entry->tail_length, entry);
434 /* pack long strings */
435 target->long_string_count = (apr_size_t)source->long_strings->nelts;
436 target->long_strings = apr_palloc(pool, sizeof(*target->long_strings) *
437 target->long_string_count);
438 for (i = 0; i < source->long_strings->nelts; ++i)
440 svn_string_t *string = &target->long_strings[i];
441 *string = APR_ARRAY_IDX(source->long_strings, i, svn_string_t);
442 string->data = apr_pstrmemdup(pool, string->data, string->len);
445 data->len += PADDING; /* add a few extra bytes at the end of the buffer
446 that we want to keep valid for chunky access */
447 assert(data->len < data->blocksize);
448 memset(data->data + data->len - PADDING, 0, PADDING);
450 target->data = apr_pmemdup(pool, data->data, data->len);
451 target->data_size = data->len;
455 svn_fs_x__string_table_create(const string_table_builder_t *builder,
460 string_table_t *result = apr_pcalloc(pool, sizeof(*result));
461 result->size = (apr_size_t)builder->tables->nelts;
463 = apr_pcalloc(pool, result->size * sizeof(*result->sub_tables));
465 for (i = 0; i < result->size; ++i)
466 create_table(&result->sub_tables[i],
467 APR_ARRAY_IDX(builder->tables, i, builder_table_t*),
474 /* Masks used by table_copy_string. copy_mask[I] is used if the target
475 content to be preserved starts at byte I within the current chunk.
476 This is used to work around alignment issues.
478 #if SVN_UNALIGNED_ACCESS_IS_OK
479 static const char *copy_masks[8] = { "\xff\xff\xff\xff\xff\xff\xff\xff",
480 "\x00\xff\xff\xff\xff\xff\xff\xff",
481 "\x00\x00\xff\xff\xff\xff\xff\xff",
482 "\x00\x00\x00\xff\xff\xff\xff\xff",
483 "\x00\x00\x00\x00\xff\xff\xff\xff",
484 "\x00\x00\x00\x00\x00\xff\xff\xff",
485 "\x00\x00\x00\x00\x00\x00\xff\xff",
486 "\x00\x00\x00\x00\x00\x00\x00\xff" };
490 table_copy_string(char *buffer,
492 const string_sub_table_t *table,
493 string_header_t *header)
498 assert(header->head_length <= len);
500 #if SVN_UNALIGNED_ACCESS_IS_OK
501 /* the sections that we copy tend to be short but we can copy
502 *all* of it chunky because we made sure that source and target
503 buffer have some extra padding to prevent segfaults. */
505 apr_size_t to_copy = len - header->head_length;
506 apr_size_t copied = 0;
508 const char *source = table->data + header->tail_start;
509 char *target = buffer + header->head_length;
510 len = header->head_length;
512 /* copy whole chunks */
513 while (to_copy >= copied + sizeof(apr_uint64_t))
515 *(apr_uint64_t *)(target + copied)
516 = *(const apr_uint64_t *)(source + copied);
517 copied += sizeof(apr_uint64_t);
520 /* copy the remainder assuming that we have up to 8 extra bytes
521 of addressable buffer on the source and target sides.
522 Now, we simply copy 8 bytes and use a mask to filter & merge
523 old with new data. */
524 mask = *(const apr_uint64_t *)copy_masks[to_copy - copied];
525 *(apr_uint64_t *)(target + copied)
526 = (*(apr_uint64_t *)(target + copied) & mask)
527 | (*(const apr_uint64_t *)(source + copied) & ~mask);
529 memcpy(buffer + header->head_length,
530 table->data + header->tail_start,
531 len - header->head_length);
532 len = header->head_length;
536 header = &table->short_strings[header->head_string];
542 svn_fs_x__string_table_get(const string_table_t *table,
547 apr_size_t table_number = idx >> TABLE_SHIFT;
548 apr_size_t sub_index = idx & STRING_INDEX_MASK;
550 if (table_number < table->size)
552 string_sub_table_t *sub_table = &table->sub_tables[table_number];
553 if (idx & LONG_STRING_MASK)
555 if (sub_index < sub_table->long_string_count)
558 *length = sub_table->long_strings[sub_index].len;
560 return apr_pstrmemdup(pool,
561 sub_table->long_strings[sub_index].data,
562 sub_table->long_strings[sub_index].len);
567 if (sub_index < sub_table->short_string_count)
569 string_header_t *header = sub_table->short_strings + sub_index;
570 apr_size_t len = header->head_length + header->tail_length;
571 char *result = apr_palloc(pool, len + PADDING);
575 table_copy_string(result, len, sub_table, header);
582 return apr_pstrmemdup(pool, "", 0);
586 svn_fs_x__write_string_table(svn_stream_t *stream,
587 const string_table_t *table,
588 apr_pool_t *scratch_pool)
592 svn_packed__data_root_t *root = svn_packed__data_create_root(scratch_pool);
594 svn_packed__int_stream_t *table_sizes
595 = svn_packed__create_int_stream(root, FALSE, FALSE);
596 svn_packed__int_stream_t *small_strings_headers
597 = svn_packed__create_int_stream(root, FALSE, FALSE);
598 svn_packed__byte_stream_t *large_strings
599 = svn_packed__create_bytes_stream(root);
600 svn_packed__byte_stream_t *small_strings_data
601 = svn_packed__create_bytes_stream(root);
603 svn_packed__create_int_substream(small_strings_headers, TRUE, FALSE);
604 svn_packed__create_int_substream(small_strings_headers, FALSE, FALSE);
605 svn_packed__create_int_substream(small_strings_headers, TRUE, FALSE);
606 svn_packed__create_int_substream(small_strings_headers, FALSE, FALSE);
608 /* number of sub-tables */
610 svn_packed__add_uint(table_sizes, table->size);
612 /* all short-string char data sizes */
614 for (i = 0; i < table->size; ++i)
615 svn_packed__add_uint(table_sizes,
616 table->sub_tables[i].short_string_count);
618 for (i = 0; i < table->size; ++i)
619 svn_packed__add_uint(table_sizes,
620 table->sub_tables[i].long_string_count);
624 for (i = 0; i < table->size; ++i)
626 string_sub_table_t *sub_table = &table->sub_tables[i];
627 svn_packed__add_bytes(small_strings_data,
629 sub_table->data_size);
631 for (k = 0; k < sub_table->short_string_count; ++k)
633 string_header_t *string = &sub_table->short_strings[k];
635 svn_packed__add_uint(small_strings_headers, string->head_string);
636 svn_packed__add_uint(small_strings_headers, string->head_length);
637 svn_packed__add_uint(small_strings_headers, string->tail_start);
638 svn_packed__add_uint(small_strings_headers, string->tail_length);
641 for (k = 0; k < sub_table->long_string_count; ++k)
642 svn_packed__add_bytes(large_strings,
643 sub_table->long_strings[k].data,
644 sub_table->long_strings[k].len + 1);
647 /* write to target stream */
649 SVN_ERR(svn_packed__data_write(stream, root, scratch_pool));
655 svn_fs_x__read_string_table(string_table_t **table_p,
656 svn_stream_t *stream,
657 apr_pool_t *result_pool,
658 apr_pool_t *scratch_pool)
662 string_table_t *table = apr_palloc(result_pool, sizeof(*table));
664 svn_packed__data_root_t *root;
665 svn_packed__int_stream_t *table_sizes;
666 svn_packed__byte_stream_t *large_strings;
667 svn_packed__byte_stream_t *small_strings_data;
668 svn_packed__int_stream_t *headers;
670 SVN_ERR(svn_packed__data_read(&root, stream, result_pool, scratch_pool));
671 table_sizes = svn_packed__first_int_stream(root);
672 headers = svn_packed__next_int_stream(table_sizes);
673 large_strings = svn_packed__first_byte_stream(root);
674 small_strings_data = svn_packed__next_byte_stream(large_strings);
676 /* create sub-tables */
678 table->size = (apr_size_t)svn_packed__get_uint(table_sizes);
679 table->sub_tables = apr_pcalloc(result_pool,
680 table->size * sizeof(*table->sub_tables));
682 /* read short strings */
684 for (i = 0; i < table->size; ++i)
686 string_sub_table_t *sub_table = &table->sub_tables[i];
688 sub_table->short_string_count
689 = (apr_size_t)svn_packed__get_uint(table_sizes);
690 if (sub_table->short_string_count)
692 sub_table->short_strings
693 = apr_pcalloc(result_pool, sub_table->short_string_count
694 * sizeof(*sub_table->short_strings));
696 /* read short string headers */
698 for (k = 0; k < sub_table->short_string_count; ++k)
700 string_header_t *string = &sub_table->short_strings[k];
702 string->head_string = (apr_uint16_t)svn_packed__get_uint(headers);
703 string->head_length = (apr_uint16_t)svn_packed__get_uint(headers);
704 string->tail_start = (apr_uint16_t)svn_packed__get_uint(headers);
705 string->tail_length = (apr_uint16_t)svn_packed__get_uint(headers);
709 sub_table->data = svn_packed__get_bytes(small_strings_data,
710 &sub_table->data_size);
713 /* read long strings */
715 for (i = 0; i < table->size; ++i)
717 /* initialize long string table */
718 string_sub_table_t *sub_table = &table->sub_tables[i];
720 sub_table->long_string_count = svn_packed__get_uint(table_sizes);
721 if (sub_table->long_string_count)
723 sub_table->long_strings
724 = apr_pcalloc(result_pool, sub_table->long_string_count
725 * sizeof(*sub_table->long_strings));
727 /* read long strings */
729 for (k = 0; k < sub_table->long_string_count; ++k)
731 svn_string_t *string = &sub_table->long_strings[k];
732 string->data = svn_packed__get_bytes(large_strings,
747 svn_fs_x__serialize_string_table(svn_temp_serializer__context_t *context,
751 string_table_t *string_table = *st;
752 if (string_table == NULL)
755 /* string table struct */
756 svn_temp_serializer__push(context,
757 (const void * const *)st,
758 sizeof(*string_table));
760 /* sub-table array (all structs in a single memory block) */
761 svn_temp_serializer__push(context,
762 (const void * const *)&string_table->sub_tables,
763 sizeof(*string_table->sub_tables) *
766 /* sub-elements of all sub-tables */
767 for (i = 0; i < string_table->size; ++i)
769 string_sub_table_t *sub_table = &string_table->sub_tables[i];
770 svn_temp_serializer__add_leaf(context,
771 (const void * const *)&sub_table->data,
772 sub_table->data_size);
773 svn_temp_serializer__add_leaf(context,
774 (const void * const *)&sub_table->short_strings,
775 sub_table->short_string_count * sizeof(string_header_t));
777 /* all "long string" instances form a single memory block */
778 svn_temp_serializer__push(context,
779 (const void * const *)&sub_table->long_strings,
780 sub_table->long_string_count * sizeof(svn_string_t));
782 /* serialize actual long string contents */
783 for (k = 0; k < sub_table->long_string_count; ++k)
785 svn_string_t *string = &sub_table->long_strings[k];
786 svn_temp_serializer__add_leaf(context,
787 (const void * const *)&string->data,
791 svn_temp_serializer__pop(context);
794 /* back to the caller's nesting level */
795 svn_temp_serializer__pop(context);
796 svn_temp_serializer__pop(context);
800 svn_fs_x__deserialize_string_table(void *buffer,
801 string_table_t **table)
804 string_sub_table_t *sub_tables;
806 svn_temp_deserializer__resolve(buffer, (void **)table);
810 svn_temp_deserializer__resolve(*table, (void **)&(*table)->sub_tables);
811 sub_tables = (*table)->sub_tables;
812 for (i = 0; i < (*table)->size; ++i)
814 string_sub_table_t *sub_table = sub_tables + i;
816 svn_temp_deserializer__resolve(sub_tables,
817 (void **)&sub_table->data);
818 svn_temp_deserializer__resolve(sub_tables,
819 (void **)&sub_table->short_strings);
820 svn_temp_deserializer__resolve(sub_tables,
821 (void **)&sub_table->long_strings);
823 for (k = 0; k < sub_table->long_string_count; ++k)
824 svn_temp_deserializer__resolve(sub_table->long_strings,
825 (void **)&sub_table->long_strings[k].data);
830 svn_fs_x__string_table_get_func(const string_table_t *table,
835 apr_size_t table_number = idx >> TABLE_SHIFT;
836 apr_size_t sub_index = idx & STRING_INDEX_MASK;
838 if (table_number < table->size)
840 /* resolve TABLE->SUB_TABLES pointer and select sub-table */
841 string_sub_table_t *sub_tables
842 = (string_sub_table_t *)svn_temp_deserializer__ptr(table,
843 (const void *const *)&table->sub_tables);
844 string_sub_table_t *sub_table = sub_tables + table_number;
846 /* pick the right kind of string */
847 if (idx & LONG_STRING_MASK)
849 if (sub_index < sub_table->long_string_count)
851 /* resolve SUB_TABLE->LONG_STRINGS, select the string we want
852 and resolve the pointer to its char data */
853 svn_string_t *long_strings
854 = (svn_string_t *)svn_temp_deserializer__ptr(sub_table,
855 (const void *const *)&sub_table->long_strings);
857 = (const char*)svn_temp_deserializer__ptr(long_strings,
858 (const void *const *)&long_strings[sub_index].data);
860 /* return a copy of the char data */
862 *length = long_strings[sub_index].len;
864 return apr_pstrmemdup(pool,
866 long_strings[sub_index].len);
871 if (sub_index < sub_table->short_string_count)
873 string_header_t *header;
877 /* construct a copy of our sub-table struct with SHORT_STRINGS
878 and DATA pointers resolved. Leave all other pointers as
879 they are. This allows us to use the same code for string
880 reconstruction here as in the non-serialized case. */
881 string_sub_table_t table_copy = *sub_table;
883 = (const char *)svn_temp_deserializer__ptr(sub_tables,
884 (const void *const *)&sub_table->data);
885 table_copy.short_strings
886 = (string_header_t *)svn_temp_deserializer__ptr(sub_tables,
887 (const void *const *)&sub_table->short_strings);
889 /* reconstruct the char data and return it */
890 header = table_copy.short_strings + sub_index;
891 len = header->head_length + header->tail_length;
892 result = apr_palloc(pool, len + PADDING);
896 table_copy_string(result, len, &table_copy, header);