]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_fs_fs/revprops.c
MFV r353630: 10809 Performance optimization of AVL tree comparator functions
[FreeBSD/FreeBSD.git] / contrib / subversion / subversion / libsvn_fs_fs / revprops.c
1 /* revprops.c --- everything needed to handle revprops in FSFS
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 <assert.h>
24
25 #include "svn_pools.h"
26 #include "svn_hash.h"
27 #include "svn_dirent_uri.h"
28 #include "svn_sorts.h"
29
30 #include "fs_fs.h"
31 #include "revprops.h"
32 #include "temp_serializer.h"
33 #include "util.h"
34
35 #include "private/svn_subr_private.h"
36 #include "private/svn_string_private.h"
37 #include "../libsvn_fs/fs-loader.h"
38
39 #include "svn_private_config.h"
40
41 svn_error_t *
42 svn_fs_fs__upgrade_pack_revprops(svn_fs_t *fs,
43                                  svn_fs_upgrade_notify_t notify_func,
44                                  void *notify_baton,
45                                  svn_cancel_func_t cancel_func,
46                                  void *cancel_baton,
47                                  apr_pool_t *scratch_pool)
48 {
49   fs_fs_data_t *ffd = fs->fsap_data;
50   const char *revprops_shard_path;
51   const char *revprops_pack_file_dir;
52   apr_int64_t shard;
53   apr_int64_t first_unpacked_shard
54     =  ffd->min_unpacked_rev / ffd->max_files_per_dir;
55
56   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
57   const char *revsprops_dir = svn_dirent_join(fs->path, PATH_REVPROPS_DIR,
58                                               scratch_pool);
59   int compression_level = ffd->compress_packed_revprops
60                            ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
61                            : SVN_DELTA_COMPRESSION_LEVEL_NONE;
62
63   /* first, pack all revprops shards to match the packed revision shards */
64   for (shard = 0; shard < first_unpacked_shard; ++shard)
65     {
66       svn_pool_clear(iterpool);
67
68       revprops_pack_file_dir = svn_dirent_join(revsprops_dir,
69                    apr_psprintf(iterpool,
70                                 "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
71                                 shard),
72                    iterpool);
73       revprops_shard_path = svn_dirent_join(revsprops_dir,
74                        apr_psprintf(iterpool, "%" APR_INT64_T_FMT, shard),
75                        iterpool);
76
77       SVN_ERR(svn_fs_fs__pack_revprops_shard(revprops_pack_file_dir,
78                                              revprops_shard_path,
79                                              shard, ffd->max_files_per_dir,
80                                              (int)(0.9 * ffd->revprop_pack_size),
81                                              compression_level,
82                                              ffd->flush_to_disk,
83                                              cancel_func, cancel_baton,
84                                              iterpool));
85       if (notify_func)
86         SVN_ERR(notify_func(notify_baton, shard,
87                             svn_fs_upgrade_pack_revprops, iterpool));
88     }
89
90   svn_pool_destroy(iterpool);
91
92   return SVN_NO_ERROR;
93 }
94
95 svn_error_t *
96 svn_fs_fs__upgrade_cleanup_pack_revprops(svn_fs_t *fs,
97                                          svn_fs_upgrade_notify_t notify_func,
98                                          void *notify_baton,
99                                          svn_cancel_func_t cancel_func,
100                                          void *cancel_baton,
101                                          apr_pool_t *scratch_pool)
102 {
103   fs_fs_data_t *ffd = fs->fsap_data;
104   const char *revprops_shard_path;
105   apr_int64_t shard;
106   apr_int64_t first_unpacked_shard
107     =  ffd->min_unpacked_rev / ffd->max_files_per_dir;
108
109   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
110   const char *revsprops_dir = svn_dirent_join(fs->path, PATH_REVPROPS_DIR,
111                                               scratch_pool);
112
113   /* delete the non-packed revprops shards afterwards */
114   for (shard = 0; shard < first_unpacked_shard; ++shard)
115     {
116       svn_pool_clear(iterpool);
117
118       revprops_shard_path = svn_dirent_join(revsprops_dir,
119                        apr_psprintf(iterpool, "%" APR_INT64_T_FMT, shard),
120                        iterpool);
121       SVN_ERR(svn_fs_fs__delete_revprops_shard(revprops_shard_path,
122                                                shard,
123                                                ffd->max_files_per_dir,
124                                                cancel_func, cancel_baton,
125                                                iterpool));
126       if (notify_func)
127         SVN_ERR(notify_func(notify_baton, shard,
128                             svn_fs_upgrade_cleanup_revprops, iterpool));
129     }
130
131   svn_pool_destroy(iterpool);
132
133   return SVN_NO_ERROR;
134 }
135
136 /* Container for all data required to access the packed revprop file
137  * for a given REVISION.  This structure will be filled incrementally
138  * by read_pack_revprops() its sub-routines.
139  */
140 typedef struct packed_revprops_t
141 {
142   /* revision number to read (not necessarily the first in the pack) */
143   svn_revnum_t revision;
144
145   /* the actual revision properties */
146   apr_hash_t *properties;
147
148   /* their size when serialized to a single string
149    * (as found in PACKED_REVPROPS) */
150   apr_size_t serialized_size;
151
152
153   /* name of the pack file (without folder path) */
154   const char *filename;
155
156   /* packed shard folder path */
157   const char *folder;
158
159   /* sum of values in SIZES */
160   apr_size_t total_size;
161
162   /* first revision in the pack (>= MANIFEST_START) */
163   svn_revnum_t start_revision;
164
165   /* size of the revprops in PACKED_REVPROPS */
166   apr_array_header_t *sizes;
167
168   /* offset of the revprops in PACKED_REVPROPS */
169   apr_array_header_t *offsets;
170
171
172   /* concatenation of the serialized representation of all revprops
173    * in the pack, i.e. the pack content without header and compression */
174   svn_stringbuf_t *packed_revprops;
175
176   /* First revision covered by MANIFEST.
177    * Will equal the shard start revision or 1, for the 1st shard. */
178   svn_revnum_t manifest_start;
179
180   /* content of the manifest.
181    * Maps long(rev - MANIFEST_START) to const char* pack file name */
182   apr_array_header_t *manifest;
183 } packed_revprops_t;
184
185 /* Parse the serialized revprops in CONTENT and return them in *PROPERTIES.
186  * Also, put them into the revprop cache, if activated, for future use.
187  *
188  * The returned hash will be allocated in RESULT_POOL, SCRATCH_POOL is being
189  * used for temporary allocations.
190  */
191 static svn_error_t *
192 parse_revprop(apr_hash_t **properties,
193               svn_fs_t *fs,
194               svn_revnum_t revision,
195               svn_string_t *content,
196               apr_pool_t *result_pool,
197               apr_pool_t *scratch_pool)
198 {
199   svn_stream_t *stream = svn_stream_from_string(content, scratch_pool);
200   *properties = apr_hash_make(result_pool);
201
202   SVN_ERR_W(svn_hash_read2(*properties, stream, SVN_HASH_TERMINATOR,
203                            result_pool),
204             apr_psprintf(scratch_pool, "Failed to parse revprops for r%ld.",
205                          revision));
206
207   return SVN_NO_ERROR;
208 }
209
210 void
211 svn_fs_fs__reset_revprop_cache(svn_fs_t *fs)
212 {
213   fs_fs_data_t *ffd = fs->fsap_data;
214   ffd->revprop_prefix = 0;
215 }
216
217 /* If FS has not a revprop cache prefix set, generate one.
218  * Always call this before accessing the revprop cache.
219  */
220 static svn_error_t *
221 prepare_revprop_cache(svn_fs_t *fs,
222                       apr_pool_t *scratch_pool)
223 {
224   fs_fs_data_t *ffd = fs->fsap_data;
225   if (!ffd->revprop_prefix)
226     SVN_ERR(svn_atomic__unique_counter(&ffd->revprop_prefix));
227
228   return SVN_NO_ERROR;
229 }
230
231 /* Store the unparsed revprop hash CONTENT for REVISION in FS's revprop
232  * cache.  If CACHED is not NULL, set *CACHED if there already is such
233  * an entry and skip the cache write in that case.  Use SCRATCH_POOL for
234  * temporary allocations. */
235 static svn_error_t *
236 cache_revprops(svn_boolean_t *is_cached,
237                svn_fs_t *fs,
238                svn_revnum_t revision,
239                svn_string_t *content,
240                apr_pool_t *scratch_pool)
241 {
242   fs_fs_data_t *ffd = fs->fsap_data;
243   pair_cache_key_t key;
244
245   /* Make sure prepare_revprop_cache() has been called. */
246   SVN_ERR_ASSERT(ffd->revprop_prefix);
247   key.revision = revision;
248   key.second = ffd->revprop_prefix;
249
250   if (is_cached)
251     {
252       SVN_ERR(svn_cache__has_key(is_cached, ffd->revprop_cache, &key,
253                                  scratch_pool));
254       if (*is_cached)
255         return SVN_NO_ERROR;
256     }
257
258   SVN_ERR(svn_cache__set(ffd->revprop_cache, &key, content, scratch_pool));
259
260   return SVN_NO_ERROR;
261 }
262
263 /* Read the non-packed revprops for revision REV in FS, put them into the
264  * revprop cache if PROPULATE_CACHE is set and return them in *PROPERTIES. 
265  *
266  * If the data could not be read due to an otherwise recoverable error,
267  * leave *PROPERTIES unchanged. No error will be returned in that case.
268  *
269  * Allocations will be done in POOL.
270  */
271 static svn_error_t *
272 read_non_packed_revprop(apr_hash_t **properties,
273                         svn_fs_t *fs,
274                         svn_revnum_t rev,
275                         svn_boolean_t populate_cache,
276                         apr_pool_t *pool)
277 {
278   svn_stringbuf_t *content = NULL;
279   apr_pool_t *iterpool = svn_pool_create(pool);
280   svn_boolean_t missing = FALSE;
281   int i;
282
283   for (i = 0;
284        i < SVN_FS_FS__RECOVERABLE_RETRY_COUNT && !missing && !content;
285        ++i)
286     {
287       svn_pool_clear(iterpool);
288       SVN_ERR(svn_fs_fs__try_stringbuf_from_file(&content,
289                               &missing,
290                               svn_fs_fs__path_revprops(fs, rev, iterpool),
291                               i + 1 < SVN_FS_FS__RECOVERABLE_RETRY_COUNT ,
292                               iterpool));
293     }
294
295   if (content)
296     {
297       svn_string_t *as_string = svn_stringbuf__morph_into_string(content);
298       SVN_ERR(parse_revprop(properties, fs, rev, as_string, pool, iterpool));
299
300       if (populate_cache)
301         SVN_ERR(cache_revprops(NULL, fs, rev, as_string, iterpool));
302     }
303
304   svn_pool_clear(iterpool);
305
306   return SVN_NO_ERROR;
307 }
308
309 /* Return the minimum length of any packed revprop file name in REVPROPS. */
310 static apr_size_t
311 get_min_filename_len(packed_revprops_t *revprops)
312 {
313   char number_buffer[SVN_INT64_BUFFER_SIZE];
314
315   /* The revprop filenames have the format <REV>.<COUNT> - with <REV> being
316    * at least the first rev in the shard and <COUNT> having at least one
317    * digit.  Thus, the minimum is 2 + #decimal places in the start rev.
318    */
319   return svn__i64toa(number_buffer, revprops->manifest_start) + 2;
320 }
321
322 /* Given FS and REVPROPS->REVISION, fill the FILENAME, FOLDER and MANIFEST
323  * members. Use RESULT_POOL for allocating results and SCRATCH_POOL for
324  * temporaries.
325  */
326 static svn_error_t *
327 get_revprop_packname(svn_fs_t *fs,
328                      packed_revprops_t *revprops,
329                      apr_pool_t *result_pool,
330                      apr_pool_t *scratch_pool)
331 {
332   fs_fs_data_t *ffd = fs->fsap_data;
333   svn_stringbuf_t *content = NULL;
334   const char *manifest_file_path;
335   int idx, rev_count;
336   char *buffer, *buffer_end;
337   const char **filenames, **filenames_end;
338   apr_size_t min_filename_len;
339
340   /* Determine the dimensions. Rev 0 is excluded from the first shard. */
341   rev_count = ffd->max_files_per_dir;
342   revprops->manifest_start
343     = revprops->revision - (revprops->revision % rev_count);
344   if (revprops->manifest_start == 0)
345     {
346       ++revprops->manifest_start;
347       --rev_count;
348     }
349
350   revprops->manifest = apr_array_make(result_pool, rev_count,
351                                       sizeof(const char*));
352
353   /* No line in the file can be less than this number of chars long. */
354   min_filename_len = get_min_filename_len(revprops);
355
356   /* Read the content of the manifest file */
357   revprops->folder
358     = svn_fs_fs__path_revprops_pack_shard(fs, revprops->revision,
359                                           result_pool);
360   manifest_file_path
361     = svn_dirent_join(revprops->folder, PATH_MANIFEST, result_pool);
362
363   SVN_ERR(svn_fs_fs__read_content(&content, manifest_file_path, result_pool));
364
365   /* There CONTENT must have a certain minimal size and there no
366    * unterminated lines at the end of the file.  Both guarantees also
367    * simplify the parser loop below.
368    */
369   if (   content->len < rev_count * (min_filename_len + 1)
370       || content->data[content->len - 1] != '\n')
371     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
372                              _("Packed revprop manifest for r%ld not "
373                                "properly terminated"), revprops->revision);
374
375   /* Chop (parse) the manifest CONTENT into filenames, one per line.
376    * We only have to replace all newlines with NUL and add all line
377    * starts to REVPROPS->MANIFEST.
378    *
379    * There must be exactly REV_COUNT lines and that is the number of
380    * lines we parse from BUFFER to FILENAMES.  Set the end pointer for
381    * the source BUFFER such that BUFFER+MIN_FILENAME_LEN is still valid
382    * BUFFER_END is always valid due to CONTENT->LEN > MIN_FILENAME_LEN.
383    *
384    * Please note that this loop is performance critical for e.g. 'svn log'.
385    * It is run 1000x per revprop access, i.e. per revision and about
386    * 50 million times per sec (and CPU core).
387    */
388   for (filenames = (const char **)revprops->manifest->elts,
389        filenames_end = filenames + rev_count,
390        buffer = content->data,
391        buffer_end = buffer + content->len - min_filename_len;
392        (filenames < filenames_end) && (buffer < buffer_end);
393        ++filenames)
394     {
395       /* BUFFER always points to the start of the next line / filename. */
396       *filenames = buffer;
397
398       /* Find the next EOL.  This is guaranteed to stay within the CONTENT
399        * buffer because we left enough room after BUFFER_END and we know
400        * we will always see a newline as the last non-NUL char. */
401       buffer += min_filename_len;
402       while (*buffer != '\n')
403         ++buffer;
404
405       /* Found EOL.  Turn it into the filename terminator and move BUFFER
406        * to the start of the next line or CONTENT buffer end. */
407       *buffer = '\0';
408       ++buffer;
409     }
410
411   /* We must have reached the end of both buffers. */
412   if (buffer < content->data + content->len)
413     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
414                              _("Packed revprop manifest for r%ld "
415                                "has too many entries"), revprops->revision);
416
417   if (filenames < filenames_end)
418     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
419                              _("Packed revprop manifest for r%ld "
420                                "has too few entries"), revprops->revision);
421
422   /* The target array has now exactly one entry per revision. */
423   revprops->manifest->nelts = rev_count;
424
425   /* Now get the file name */
426   idx = (int)(revprops->revision - revprops->manifest_start);
427   revprops->filename = APR_ARRAY_IDX(revprops->manifest, idx, const char*);
428
429   return SVN_NO_ERROR;
430 }
431
432 /* Return TRUE, if revision R1 and R2 refer to the same shard in FS.
433  */
434 static svn_boolean_t
435 same_shard(svn_fs_t *fs,
436            svn_revnum_t r1,
437            svn_revnum_t r2)
438 {
439   fs_fs_data_t *ffd = fs->fsap_data;
440   return (r1 / ffd->max_files_per_dir) == (r2 / ffd->max_files_per_dir);
441 }
442
443 /* Given FS and the full packed file content in REVPROPS->PACKED_REVPROPS,
444  * fill the START_REVISION member, and make PACKED_REVPROPS point to the
445  * first serialized revprop.  If READ_ALL is set, initialize the SIZES
446  * and OFFSETS members as well.  If POPULATE_CACHE is set, cache all
447  * revprops found in this pack.
448  *
449  * Parse the revprops for REVPROPS->REVISION and set the PROPERTIES as
450  * well as the SERIALIZED_SIZE member.  If revprop caching has been
451  * enabled, parse all revprops in the pack and cache them.
452  */
453 static svn_error_t *
454 parse_packed_revprops(svn_fs_t *fs,
455                       packed_revprops_t *revprops,
456                       svn_boolean_t read_all,
457                       svn_boolean_t populate_cache,
458                       apr_pool_t *result_pool,
459                       apr_pool_t *scratch_pool)
460 {
461   svn_stream_t *stream;
462   apr_int64_t first_rev, count, i;
463   apr_size_t offset;
464   const char *header_end;
465   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
466
467   /* Initial value for the "Leaking bucket" pattern. */
468   int bucket = 4;
469
470   /* decompress (even if the data is only "stored", there is still a
471    * length header to remove) */
472   svn_stringbuf_t *compressed = revprops->packed_revprops;
473   svn_stringbuf_t *uncompressed = svn_stringbuf_create_empty(result_pool);
474   SVN_ERR(svn__decompress_zlib(compressed->data, compressed->len,
475                                uncompressed, APR_SIZE_MAX));
476
477   /* read first revision number and number of revisions in the pack */
478   stream = svn_stream_from_stringbuf(uncompressed, scratch_pool);
479   SVN_ERR(svn_fs_fs__read_number_from_stream(&first_rev, NULL, stream,
480                                              iterpool));
481   SVN_ERR(svn_fs_fs__read_number_from_stream(&count, NULL, stream,
482                                              iterpool));
483
484   /* Check revision range for validity. */
485   if (   !same_shard(fs, revprops->revision, first_rev)
486       || !same_shard(fs, revprops->revision, first_rev + count - 1)
487       || count < 1)
488     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
489                              _("Revprop pack for revision r%ld"
490                                " contains revprops for r%ld .. r%ld"),
491                              revprops->revision,
492                              (svn_revnum_t)first_rev,
493                              (svn_revnum_t)(first_rev + count -1));
494
495   /* Since start & end are in the same shard, it is enough to just test
496    * the FIRST_REV for being actually packed.  That will also cover the
497    * special case of rev 0 never being packed. */
498   if (!svn_fs_fs__is_packed_revprop(fs, first_rev))
499     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
500                              _("Revprop pack for revision r%ld"
501                                " starts at non-packed revisions r%ld"),
502                              revprops->revision, (svn_revnum_t)first_rev);
503
504   /* make PACKED_REVPROPS point to the first char after the header.
505    * This is where the serialized revprops are. */
506   header_end = strstr(uncompressed->data, "\n\n");
507   if (header_end == NULL)
508     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
509                             _("Header end not found"));
510
511   offset = header_end - uncompressed->data + 2;
512
513   revprops->packed_revprops = svn_stringbuf_create_empty(result_pool);
514   revprops->packed_revprops->data = uncompressed->data + offset;
515   revprops->packed_revprops->len = (apr_size_t)(uncompressed->len - offset);
516   revprops->packed_revprops->blocksize = (apr_size_t)(uncompressed->blocksize
517                                                       - offset);
518
519   /* STREAM still points to the first entry in the sizes list. */
520   revprops->start_revision = (svn_revnum_t)first_rev;
521   if (read_all)
522     {
523       /* Init / construct REVPROPS members. */
524       revprops->sizes = apr_array_make(result_pool, (int)count,
525                                        sizeof(offset));
526       revprops->offsets = apr_array_make(result_pool, (int)count,
527                                          sizeof(offset));
528     }
529
530   /* Now parse, revision by revision, the size and content of each
531    * revisions' revprops. */
532   for (i = 0, offset = 0, revprops->total_size = 0; i < count; ++i)
533     {
534       apr_int64_t size;
535       svn_string_t serialized;
536       svn_revnum_t revision = (svn_revnum_t)(first_rev + i);
537       svn_pool_clear(iterpool);
538
539       /* read & check the serialized size */
540       SVN_ERR(svn_fs_fs__read_number_from_stream(&size, NULL, stream,
541                                                  iterpool));
542       if (size > (apr_int64_t)revprops->packed_revprops->len - offset)
543         return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
544                         _("Packed revprop size exceeds pack file size"));
545
546       /* Parse this revprops list, if necessary */
547       serialized.data = revprops->packed_revprops->data + offset;
548       serialized.len = (apr_size_t)size;
549
550       if (revision == revprops->revision)
551         {
552           /* Parse (and possibly cache) the one revprop list we care about. */
553           SVN_ERR(parse_revprop(&revprops->properties, fs, revision,
554                                 &serialized, result_pool, iterpool));
555           revprops->serialized_size = serialized.len;
556
557           /* If we only wanted the revprops for REVISION then we are done. */
558           if (!read_all && !populate_cache)
559             break;
560         }
561
562       if (populate_cache)
563         {
564           /* Adding all those revprops is expensive, in particular in a
565            * multi-threaded environment.  There are situations where hit
566            * rates are low and revprops get evicted before re-using them.
567            *
568            * We try to detect thosse cases here.
569            * Only keep going while most (at least 2/3) aren't cached, yet. */
570           svn_boolean_t already_cached;
571           SVN_ERR(cache_revprops(&already_cached, fs, revision, &serialized,
572                                  iterpool));
573
574           /* Stop populating the cache once we encountered too many entries
575            * already present relative to the numbers being added. */
576           if (!already_cached)
577             {
578               ++bucket;
579             }
580           else
581             {
582               bucket -= 2;
583               if (bucket < 0)
584                 populate_cache = FALSE;
585             }
586         }
587
588       if (read_all)
589         {
590           /* fill REVPROPS data structures */
591           APR_ARRAY_PUSH(revprops->sizes, apr_size_t) = serialized.len;
592           APR_ARRAY_PUSH(revprops->offsets, apr_size_t) = offset;
593         }
594       revprops->total_size += serialized.len;
595
596       offset += serialized.len;
597     }
598
599   return SVN_NO_ERROR;
600 }
601
602 /* In filesystem FS, read the packed revprops for revision REV into
603  * *REVPROPS. Populate the revprop cache, if POPULATE_CACHE is set.
604  * If you want to modify revprop contents / update REVPROPS, READ_ALL
605  * must be set.  Otherwise, only the properties of REV are being provided.
606  * Allocate data in POOL.
607  */
608 static svn_error_t *
609 read_pack_revprop(packed_revprops_t **revprops,
610                   svn_fs_t *fs,
611                   svn_revnum_t rev,
612                   svn_boolean_t read_all,
613                   svn_boolean_t populate_cache,
614                   apr_pool_t *pool)
615 {
616   apr_pool_t *iterpool = svn_pool_create(pool);
617   svn_boolean_t missing = FALSE;
618   svn_error_t *err;
619   packed_revprops_t *result;
620   int i;
621
622   /* someone insisted that REV is packed. Double-check if necessary */
623   if (!svn_fs_fs__is_packed_revprop(fs, rev))
624      SVN_ERR(svn_fs_fs__update_min_unpacked_rev(fs, iterpool));
625
626   if (!svn_fs_fs__is_packed_revprop(fs, rev))
627     return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
628                               _("No such packed revision %ld"), rev);
629
630   /* initialize the result data structure */
631   result = apr_pcalloc(pool, sizeof(*result));
632   result->revision = rev;
633
634   /* try to read the packed revprops. This may require retries if we have
635    * concurrent writers. */
636   for (i = 0;
637        i < SVN_FS_FS__RECOVERABLE_RETRY_COUNT && !result->packed_revprops;
638        ++i)
639     {
640       const char *file_path;
641       svn_pool_clear(iterpool);
642
643       /* there might have been concurrent writes.
644        * Re-read the manifest and the pack file.
645        */
646       SVN_ERR(get_revprop_packname(fs, result, pool, iterpool));
647       file_path  = svn_dirent_join(result->folder,
648                                    result->filename,
649                                    iterpool);
650       SVN_ERR(svn_fs_fs__try_stringbuf_from_file(&result->packed_revprops,
651                                 &missing,
652                                 file_path,
653                                 i + 1 < SVN_FS_FS__RECOVERABLE_RETRY_COUNT,
654                                 pool));
655     }
656
657   /* the file content should be available now */
658   if (!result->packed_revprops)
659     return svn_error_createf(SVN_ERR_FS_PACKED_REVPROP_READ_FAILURE, NULL,
660                   _("Failed to read revprop pack file for r%ld"), rev);
661
662   /* parse it. RESULT will be complete afterwards. */
663   err = parse_packed_revprops(fs, result, read_all, populate_cache, pool,
664                               iterpool);
665   svn_pool_destroy(iterpool);
666   if (err)
667     return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
668                   _("Revprop pack file for r%ld is corrupt"), rev);
669
670   *revprops = result;
671
672   return SVN_NO_ERROR;
673 }
674
675 /* Read the revprops for revision REV in FS and return them in *PROPERTIES_P.
676  *
677  * Allocations will be done in POOL.
678  */
679 svn_error_t *
680 svn_fs_fs__get_revision_proplist(apr_hash_t **proplist_p,
681                                  svn_fs_t *fs,
682                                  svn_revnum_t rev,
683                                  svn_boolean_t refresh,
684                                  apr_pool_t *result_pool,
685                                  apr_pool_t *scratch_pool)
686 {
687   fs_fs_data_t *ffd = fs->fsap_data;
688
689   /* Only populate the cache if we did not just cross a sync barrier.
690    * This is to eliminate overhead from code that always sets REFRESH.
691    * For callers that want caching, the caching kicks in on read "later". */
692   svn_boolean_t populate_cache = !refresh;
693
694   /* not found, yet */
695   *proplist_p = NULL;
696
697   /* should they be available at all? */
698   SVN_ERR(svn_fs_fs__ensure_revision_exists(rev, fs, scratch_pool));
699
700   if (refresh)
701     {
702       /* Previous cache contents is invalid now. */
703       svn_fs_fs__reset_revprop_cache(fs);
704     }
705   else
706     {
707       /* Try cache lookup first. */
708       svn_boolean_t is_cached;
709       pair_cache_key_t key;
710
711       /* Auto-alloc prefix and construct the key. */
712       SVN_ERR(prepare_revprop_cache(fs, scratch_pool));
713       key.revision = rev;
714       key.second = ffd->revprop_prefix;
715
716       /* The only way that this might error out is due to parser error. */
717       SVN_ERR_W(svn_cache__get((void **) proplist_p, &is_cached,
718                                ffd->revprop_cache, &key, result_pool),
719                 apr_psprintf(scratch_pool,
720                              "Failed to parse revprops for r%ld.",
721                              rev));
722       if (is_cached)
723         return SVN_NO_ERROR;
724     }
725
726   /* if REV had not been packed when we began, try reading it from the
727    * non-packed shard.  If that fails, we will fall through to packed
728    * shard reads. */
729   if (!svn_fs_fs__is_packed_revprop(fs, rev))
730     {
731       svn_error_t *err = read_non_packed_revprop(proplist_p, fs, rev,
732                                                  populate_cache, result_pool);
733       if (err)
734         {
735           if (!APR_STATUS_IS_ENOENT(err->apr_err)
736               || ffd->format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
737             return svn_error_trace(err);
738
739           svn_error_clear(err);
740           *proplist_p = NULL; /* in case read_non_packed_revprop changed it */
741         }
742     }
743
744   /* if revprop packing is available and we have not read the revprops, yet,
745    * try reading them from a packed shard.  If that fails, REV is most
746    * likely invalid (or its revprops highly contested). */
747   if (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT && !*proplist_p)
748     {
749       packed_revprops_t *revprops;
750       SVN_ERR(read_pack_revprop(&revprops, fs, rev, FALSE, populate_cache,
751                                 result_pool));
752       *proplist_p = revprops->properties;
753     }
754
755   /* The revprops should have been there. Did we get them? */
756   if (!*proplist_p)
757     return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
758                              _("Could not read revprops for revision %ld"),
759                              rev);
760
761   return SVN_NO_ERROR;
762 }
763
764 /* Serialize the revision property list PROPLIST of revision REV in
765  * filesystem FS to a non-packed file.  Return the name of that temporary
766  * file in *TMP_PATH and the file path that it must be moved to in
767  * *FINAL_PATH.
768  *
769  * Use POOL for allocations.
770  */
771 static svn_error_t *
772 write_non_packed_revprop(const char **final_path,
773                          const char **tmp_path,
774                          svn_fs_t *fs,
775                          svn_revnum_t rev,
776                          apr_hash_t *proplist,
777                          apr_pool_t *pool)
778 {
779   fs_fs_data_t *ffd = fs->fsap_data;
780   apr_file_t *file;
781   svn_stream_t *stream;
782   *final_path = svn_fs_fs__path_revprops(fs, rev, pool);
783
784   /* ### do we have a directory sitting around already? we really shouldn't
785      ### have to get the dirname here. */
786   SVN_ERR(svn_io_open_unique_file3(&file, tmp_path,
787                                    svn_dirent_dirname(*final_path, pool),
788                                    svn_io_file_del_none, pool, pool));
789   stream = svn_stream_from_aprfile2(file, TRUE, pool);
790   SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, pool));
791   SVN_ERR(svn_stream_close(stream));
792
793   /* Flush temporary file to disk and close it. */
794   if (ffd->flush_to_disk)
795     SVN_ERR(svn_io_file_flush_to_disk(file, pool));
796   SVN_ERR(svn_io_file_close(file, pool));
797
798   return SVN_NO_ERROR;
799 }
800
801 /* After writing the new revprop file(s), call this function to move the
802  * file at TMP_PATH to FINAL_PATH and give it the permissions from
803  * PERMS_REFERENCE.
804  *
805  * Finally, delete all the temporary files given in FILES_TO_DELETE.
806  * The latter may be NULL.
807  *
808  * Use POOL for temporary allocations.
809  */
810 static svn_error_t *
811 switch_to_new_revprop(svn_fs_t *fs,
812                       const char *final_path,
813                       const char *tmp_path,
814                       const char *perms_reference,
815                       apr_array_header_t *files_to_delete,
816                       apr_pool_t *pool)
817 {
818   fs_fs_data_t *ffd = fs->fsap_data;
819
820   SVN_ERR(svn_fs_fs__move_into_place(tmp_path, final_path, perms_reference,
821                                      ffd->flush_to_disk, pool));
822
823   /* Clean up temporary files, if necessary. */
824   if (files_to_delete)
825     {
826       apr_pool_t *iterpool = svn_pool_create(pool);
827       int i;
828
829       for (i = 0; i < files_to_delete->nelts; ++i)
830         {
831           const char *path = APR_ARRAY_IDX(files_to_delete, i, const char*);
832
833           svn_pool_clear(iterpool);
834           SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
835         }
836
837       svn_pool_destroy(iterpool);
838     }
839   return SVN_NO_ERROR;
840 }
841
842 /* Write a pack file header to STREAM that starts at revision START_REVISION
843  * and contains the indexes [START,END) of SIZES.
844  */
845 static svn_error_t *
846 serialize_revprops_header(svn_stream_t *stream,
847                           svn_revnum_t start_revision,
848                           apr_array_header_t *sizes,
849                           int start,
850                           int end,
851                           apr_pool_t *pool)
852 {
853   apr_pool_t *iterpool = svn_pool_create(pool);
854   int i;
855
856   SVN_ERR_ASSERT(start < end);
857
858   /* start revision and entry count */
859   SVN_ERR(svn_stream_printf(stream, pool, "%ld\n", start_revision));
860   SVN_ERR(svn_stream_printf(stream, pool, "%d\n", end - start));
861
862   /* the sizes array */
863   for (i = start; i < end; ++i)
864     {
865       /* Non-standard pool usage.
866        *
867        * We only allocate a few bytes each iteration -- even with a
868        * million iterations we would still be in good shape memory-wise.
869        */
870       apr_size_t size = APR_ARRAY_IDX(sizes, i, apr_size_t);
871       SVN_ERR(svn_stream_printf(stream, iterpool, "%" APR_SIZE_T_FMT "\n",
872                                 size));
873     }
874
875   /* the double newline char indicates the end of the header */
876   SVN_ERR(svn_stream_puts(stream, "\n"));
877
878   svn_pool_destroy(iterpool);
879   return SVN_NO_ERROR;
880 }
881
882 /* Writes the a pack file to FILE.  It copies the serialized data
883  * from REVPROPS for the indexes [START,END) except for index CHANGED_INDEX.
884  *
885  * The data for the latter is taken from NEW_SERIALIZED.  Note, that
886  * CHANGED_INDEX may be outside the [START,END) range, i.e. no new data is
887  * taken in that case but only a subset of the old data will be copied.
888  *
889  * NEW_TOTAL_SIZE is a hint for pre-allocating buffers of appropriate size.
890  * POOL is used for temporary allocations.
891  */
892 static svn_error_t *
893 repack_revprops(svn_fs_t *fs,
894                 packed_revprops_t *revprops,
895                 int start,
896                 int end,
897                 int changed_index,
898                 svn_stringbuf_t *new_serialized,
899                 apr_size_t new_total_size,
900                 apr_file_t *file,
901                 apr_pool_t *pool)
902 {
903   fs_fs_data_t *ffd = fs->fsap_data;
904   svn_stream_t *stream;
905   int i;
906
907   /* create data empty buffers and the stream object */
908   svn_stringbuf_t *uncompressed
909     = svn_stringbuf_create_ensure((apr_size_t)new_total_size, pool);
910   svn_stringbuf_t *compressed
911     = svn_stringbuf_create_empty(pool);
912   stream = svn_stream_from_stringbuf(uncompressed, pool);
913
914   /* write the header*/
915   SVN_ERR(serialize_revprops_header(stream, revprops->start_revision + start,
916                                     revprops->sizes, start, end, pool));
917
918   /* append the serialized revprops */
919   for (i = start; i < end; ++i)
920     if (i == changed_index)
921       {
922         SVN_ERR(svn_stream_write(stream,
923                                  new_serialized->data,
924                                  &new_serialized->len));
925       }
926     else
927       {
928         apr_size_t size = APR_ARRAY_IDX(revprops->sizes, i, apr_size_t);
929         apr_size_t offset = APR_ARRAY_IDX(revprops->offsets, i, apr_size_t);
930
931         SVN_ERR(svn_stream_write(stream,
932                                  revprops->packed_revprops->data + offset,
933                                  &size));
934       }
935
936   /* flush the stream buffer (if any) to our underlying data buffer */
937   SVN_ERR(svn_stream_close(stream));
938
939   /* compress / store the data */
940   SVN_ERR(svn__compress_zlib(uncompressed->data, uncompressed->len,
941                              compressed,
942                              ffd->compress_packed_revprops
943                                ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
944                                : SVN_DELTA_COMPRESSION_LEVEL_NONE));
945
946   /* finally, write the content to the target file, flush and close it */
947   SVN_ERR(svn_io_file_write_full(file, compressed->data, compressed->len,
948                                  NULL, pool));
949   if (ffd->flush_to_disk)
950     SVN_ERR(svn_io_file_flush_to_disk(file, pool));
951   SVN_ERR(svn_io_file_close(file, pool));
952
953   return SVN_NO_ERROR;
954 }
955
956 /* Allocate a new pack file name for revisions
957  *     [REVPROPS->START_REVISION + START, REVPROPS->START_REVISION + END - 1]
958  * of REVPROPS->MANIFEST.  Add the name of old file to FILES_TO_DELETE,
959  * auto-create that array if necessary.  Return an open file *FILE that is
960  * allocated in POOL.
961  */
962 static svn_error_t *
963 repack_file_open(apr_file_t **file,
964                  svn_fs_t *fs,
965                  packed_revprops_t *revprops,
966                  int start,
967                  int end,
968                  apr_array_header_t **files_to_delete,
969                  apr_pool_t *pool)
970 {
971   apr_int64_t tag;
972   const char *tag_string;
973   const char *new_filename;
974   int i;
975   int manifest_offset
976     = (int)(revprops->start_revision - revprops->manifest_start);
977
978   /* get the old (= current) file name and enlist it for later deletion */
979   const char *old_filename = APR_ARRAY_IDX(revprops->manifest,
980                                            start + manifest_offset,
981                                            const char*);
982
983   if (*files_to_delete == NULL)
984     *files_to_delete = apr_array_make(pool, 3, sizeof(const char*));
985
986   APR_ARRAY_PUSH(*files_to_delete, const char*)
987     = svn_dirent_join(revprops->folder, old_filename, pool);
988
989   /* increase the tag part, i.e. the counter after the dot */
990   tag_string = strchr(old_filename, '.');
991   if (tag_string == NULL)
992     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
993                              _("Packed file '%s' misses a tag"),
994                              old_filename);
995
996   SVN_ERR(svn_cstring_atoi64(&tag, tag_string + 1));
997   new_filename = apr_psprintf(pool, "%ld.%" APR_INT64_T_FMT,
998                               revprops->start_revision + start,
999                               ++tag);
1000
1001   /* update the manifest to point to the new file */
1002   for (i = start; i < end; ++i)
1003     APR_ARRAY_IDX(revprops->manifest, i + manifest_offset, const char*)
1004       = new_filename;
1005
1006   /* open the file */
1007   SVN_ERR(svn_io_file_open(file, svn_dirent_join(revprops->folder,
1008                                                  new_filename,
1009                                                  pool),
1010                            APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool));
1011
1012   return SVN_NO_ERROR;
1013 }
1014
1015 /* For revision REV in filesystem FS, set the revision properties to
1016  * PROPLIST.  Return a new file in *TMP_PATH that the caller shall move
1017  * to *FINAL_PATH to make the change visible.  Files to be deleted will
1018  * be listed in *FILES_TO_DELETE which may remain unchanged / unallocated.
1019  * Use POOL for allocations.
1020  */
1021 static svn_error_t *
1022 write_packed_revprop(const char **final_path,
1023                      const char **tmp_path,
1024                      apr_array_header_t **files_to_delete,
1025                      svn_fs_t *fs,
1026                      svn_revnum_t rev,
1027                      apr_hash_t *proplist,
1028                      apr_pool_t *pool)
1029 {
1030   fs_fs_data_t *ffd = fs->fsap_data;
1031   packed_revprops_t *revprops;
1032   svn_stream_t *stream;
1033   apr_file_t *file;
1034   svn_stringbuf_t *serialized;
1035   apr_size_t new_total_size;
1036   int changed_index;
1037
1038   /* read contents of the current pack file */
1039   SVN_ERR(read_pack_revprop(&revprops, fs, rev, TRUE, FALSE, pool));
1040
1041   /* serialize the new revprops */
1042   serialized = svn_stringbuf_create_empty(pool);
1043   stream = svn_stream_from_stringbuf(serialized, pool);
1044   SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, pool));
1045   SVN_ERR(svn_stream_close(stream));
1046
1047   /* calculate the size of the new data */
1048   changed_index = (int)(rev - revprops->start_revision);
1049   new_total_size = revprops->total_size - revprops->serialized_size
1050                  + serialized->len
1051                  + (revprops->offsets->nelts + 2) * SVN_INT64_BUFFER_SIZE;
1052
1053   APR_ARRAY_IDX(revprops->sizes, changed_index, apr_size_t) = serialized->len;
1054
1055   /* can we put the new data into the same pack as the before? */
1056   if (   new_total_size < ffd->revprop_pack_size
1057       || revprops->sizes->nelts == 1)
1058     {
1059       /* simply replace the old pack file with new content as we do it
1060        * in the non-packed case */
1061
1062       *final_path = svn_dirent_join(revprops->folder, revprops->filename,
1063                                     pool);
1064       SVN_ERR(svn_io_open_unique_file3(&file, tmp_path, revprops->folder,
1065                                        svn_io_file_del_none, pool, pool));
1066       SVN_ERR(repack_revprops(fs, revprops, 0, revprops->sizes->nelts,
1067                               changed_index, serialized, new_total_size,
1068                               file, pool));
1069     }
1070   else
1071     {
1072       /* split the pack file into two of roughly equal size */
1073       int right_count, left_count, i;
1074
1075       int left = 0;
1076       int right = revprops->sizes->nelts - 1;
1077       apr_size_t left_size = 2 * SVN_INT64_BUFFER_SIZE;
1078       apr_size_t right_size = 2 * SVN_INT64_BUFFER_SIZE;
1079
1080       /* let left and right side grow such that their size difference
1081        * is minimal after each step. */
1082       while (left <= right)
1083         if (  left_size + APR_ARRAY_IDX(revprops->sizes, left, apr_size_t)
1084             < right_size + APR_ARRAY_IDX(revprops->sizes, right, apr_size_t))
1085           {
1086             left_size += APR_ARRAY_IDX(revprops->sizes, left, apr_size_t)
1087                       + SVN_INT64_BUFFER_SIZE;
1088             ++left;
1089           }
1090         else
1091           {
1092             right_size += APR_ARRAY_IDX(revprops->sizes, right, apr_size_t)
1093                        + SVN_INT64_BUFFER_SIZE;
1094             --right;
1095           }
1096
1097        /* since the items need much less than SVN_INT64_BUFFER_SIZE
1098         * bytes to represent their length, the split may not be optimal */
1099       left_count = left;
1100       right_count = revprops->sizes->nelts - left;
1101
1102       /* if new_size is large, one side may exceed the pack size limit.
1103        * In that case, split before and after the modified revprop.*/
1104       if (   left_size > ffd->revprop_pack_size
1105           || right_size > ffd->revprop_pack_size)
1106         {
1107           left_count = changed_index;
1108           right_count = revprops->sizes->nelts - left_count - 1;
1109         }
1110
1111       /* write the new, split files */
1112       if (left_count)
1113         {
1114           SVN_ERR(repack_file_open(&file, fs, revprops, 0,
1115                                    left_count, files_to_delete, pool));
1116           SVN_ERR(repack_revprops(fs, revprops, 0, left_count,
1117                                   changed_index, serialized, new_total_size,
1118                                   file, pool));
1119         }
1120
1121       if (left_count + right_count < revprops->sizes->nelts)
1122         {
1123           SVN_ERR(repack_file_open(&file, fs, revprops, changed_index,
1124                                    changed_index + 1, files_to_delete,
1125                                    pool));
1126           SVN_ERR(repack_revprops(fs, revprops, changed_index,
1127                                   changed_index + 1,
1128                                   changed_index, serialized, new_total_size,
1129                                   file, pool));
1130         }
1131
1132       if (right_count)
1133         {
1134           SVN_ERR(repack_file_open(&file, fs, revprops,
1135                                    revprops->sizes->nelts - right_count,
1136                                    revprops->sizes->nelts,
1137                                    files_to_delete, pool));
1138           SVN_ERR(repack_revprops(fs, revprops,
1139                                   revprops->sizes->nelts - right_count,
1140                                   revprops->sizes->nelts, changed_index,
1141                                   serialized, new_total_size, file,
1142                                   pool));
1143         }
1144
1145       /* write the new manifest */
1146       *final_path = svn_dirent_join(revprops->folder, PATH_MANIFEST, pool);
1147       SVN_ERR(svn_io_open_unique_file3(&file, tmp_path, revprops->folder,
1148                                        svn_io_file_del_none, pool, pool));
1149       stream = svn_stream_from_aprfile2(file, TRUE, pool);
1150       for (i = 0; i < revprops->manifest->nelts; ++i)
1151         {
1152           const char *filename = APR_ARRAY_IDX(revprops->manifest, i,
1153                                                const char*);
1154           SVN_ERR(svn_stream_printf(stream, pool, "%s\n", filename));
1155         }
1156       SVN_ERR(svn_stream_close(stream));
1157       if (ffd->flush_to_disk)
1158         SVN_ERR(svn_io_file_flush_to_disk(file, pool));
1159       SVN_ERR(svn_io_file_close(file, pool));
1160     }
1161
1162   return SVN_NO_ERROR;
1163 }
1164
1165 /* Set the revision property list of revision REV in filesystem FS to
1166    PROPLIST.  Use POOL for temporary allocations. */
1167 svn_error_t *
1168 svn_fs_fs__set_revision_proplist(svn_fs_t *fs,
1169                                  svn_revnum_t rev,
1170                                  apr_hash_t *proplist,
1171                                  apr_pool_t *pool)
1172 {
1173   svn_boolean_t is_packed;
1174   const char *final_path;
1175   const char *tmp_path;
1176   const char *perms_reference;
1177   apr_array_header_t *files_to_delete = NULL;
1178
1179   SVN_ERR(svn_fs_fs__ensure_revision_exists(rev, fs, pool));
1180
1181   /* this info will not change while we hold the global FS write lock */
1182   is_packed = svn_fs_fs__is_packed_revprop(fs, rev);
1183
1184   /* Serialize the new revprop data */
1185   if (is_packed)
1186     SVN_ERR(write_packed_revprop(&final_path, &tmp_path, &files_to_delete,
1187                                  fs, rev, proplist, pool));
1188   else
1189     SVN_ERR(write_non_packed_revprop(&final_path, &tmp_path,
1190                                      fs, rev, proplist, pool));
1191
1192   /* Previous cache contents is invalid now. */
1193   svn_fs_fs__reset_revprop_cache(fs);
1194
1195   /* We use the rev file of this revision as the perms reference,
1196    * because when setting revprops for the first time, the revprop
1197    * file won't exist and therefore can't serve as its own reference.
1198    * (Whereas the rev file should already exist at this point.)
1199    */
1200   perms_reference = svn_fs_fs__path_rev_absolute(fs, rev, pool);
1201
1202   /* Now, switch to the new revprop data. */
1203   SVN_ERR(switch_to_new_revprop(fs, final_path, tmp_path, perms_reference,
1204                                 files_to_delete, pool));
1205
1206   return SVN_NO_ERROR;
1207 }
1208
1209 /* Return TRUE, if for REVISION in FS, we can find the revprop pack file.
1210  * Use POOL for temporary allocations.
1211  * Set *MISSING, if the reason is a missing manifest or pack file.
1212  */
1213 svn_boolean_t
1214 svn_fs_fs__packed_revprop_available(svn_boolean_t *missing,
1215                                     svn_fs_t *fs,
1216                                     svn_revnum_t revision,
1217                                     apr_pool_t *pool)
1218 {
1219   fs_fs_data_t *ffd = fs->fsap_data;
1220   svn_stringbuf_t *content = NULL;
1221
1222   /* try to read the manifest file */
1223   const char *folder
1224     = svn_fs_fs__path_revprops_pack_shard(fs, revision, pool);
1225   const char *manifest_path = svn_dirent_join(folder, PATH_MANIFEST, pool);
1226
1227   svn_error_t *err = svn_fs_fs__try_stringbuf_from_file(&content,
1228                                                         missing,
1229                                                         manifest_path,
1230                                                         FALSE,
1231                                                         pool);
1232
1233   /* if the manifest cannot be read, consider the pack files inaccessible
1234    * even if the file itself exists. */
1235   if (err)
1236     {
1237       svn_error_clear(err);
1238       return FALSE;
1239     }
1240
1241   if (*missing)
1242     return FALSE;
1243
1244   /* parse manifest content until we find the entry for REVISION.
1245    * Revision 0 is never packed. */
1246   revision = revision < ffd->max_files_per_dir
1247            ? revision - 1
1248            : revision % ffd->max_files_per_dir;
1249   while (content->data)
1250     {
1251       char *next = strchr(content->data, '\n');
1252       if (next)
1253         {
1254           *next = 0;
1255           ++next;
1256         }
1257
1258       if (revision-- == 0)
1259         {
1260           /* the respective pack file must exist (and be a file) */
1261           svn_node_kind_t kind;
1262           err = svn_io_check_path(svn_dirent_join(folder, content->data,
1263                                                   pool),
1264                                   &kind, pool);
1265           if (err)
1266             {
1267               svn_error_clear(err);
1268               return FALSE;
1269             }
1270
1271           *missing = kind == svn_node_none;
1272           return kind == svn_node_file;
1273         }
1274
1275       content->data = next;
1276     }
1277
1278   return FALSE;
1279 }
1280
1281 \f
1282 /****** Packing FSFS shards *********/
1283
1284 svn_error_t *
1285 svn_fs_fs__copy_revprops(const char *pack_file_dir,
1286                          const char *pack_filename,
1287                          const char *shard_path,
1288                          svn_revnum_t start_rev,
1289                          svn_revnum_t end_rev,
1290                          apr_array_header_t *sizes,
1291                          apr_size_t total_size,
1292                          int compression_level,
1293                          svn_boolean_t flush_to_disk,
1294                          svn_cancel_func_t cancel_func,
1295                          void *cancel_baton,
1296                          apr_pool_t *scratch_pool)
1297 {
1298   svn_stream_t *pack_stream;
1299   apr_file_t *pack_file;
1300   svn_revnum_t rev;
1301   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1302
1303   /* create empty data buffer and a write stream on top of it */
1304   svn_stringbuf_t *uncompressed
1305     = svn_stringbuf_create_ensure(total_size, scratch_pool);
1306   svn_stringbuf_t *compressed
1307     = svn_stringbuf_create_empty(scratch_pool);
1308   pack_stream = svn_stream_from_stringbuf(uncompressed, scratch_pool);
1309
1310   /* write the pack file header */
1311   SVN_ERR(serialize_revprops_header(pack_stream, start_rev, sizes, 0,
1312                                     sizes->nelts, iterpool));
1313
1314   /* Some useful paths. */
1315   SVN_ERR(svn_io_file_open(&pack_file, svn_dirent_join(pack_file_dir,
1316                                                        pack_filename,
1317                                                        scratch_pool),
1318                            APR_WRITE | APR_CREATE, APR_OS_DEFAULT,
1319                            scratch_pool));
1320
1321   /* Iterate over the revisions in this shard, squashing them together. */
1322   for (rev = start_rev; rev <= end_rev; rev++)
1323     {
1324       const char *path;
1325       svn_stream_t *stream;
1326       apr_file_t *file;
1327
1328       svn_pool_clear(iterpool);
1329
1330       /* Construct the file name. */
1331       path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
1332                              iterpool);
1333
1334       /* Copy all the bits from the non-packed revprop file to the end of
1335        * the pack file.  Use unbuffered apr_file_t since we're going to
1336        * write using 16kb chunks. */
1337       SVN_ERR(svn_io_file_open(&file, path, APR_READ, APR_OS_DEFAULT,
1338                                iterpool));
1339       stream = svn_stream_from_aprfile2(file, FALSE, iterpool);
1340       SVN_ERR(svn_stream_copy3(stream, pack_stream,
1341                                cancel_func, cancel_baton, iterpool));
1342     }
1343
1344   /* flush stream buffers to content buffer */
1345   SVN_ERR(svn_stream_close(pack_stream));
1346
1347   /* compress the content (or just store it for COMPRESSION_LEVEL 0) */
1348   SVN_ERR(svn__compress_zlib(uncompressed->data, uncompressed->len,
1349                              compressed, compression_level));
1350
1351   /* write the pack file content to disk */
1352   SVN_ERR(svn_io_file_write_full(pack_file, compressed->data, compressed->len,
1353                                  NULL, scratch_pool));
1354   if (flush_to_disk)
1355     SVN_ERR(svn_io_file_flush_to_disk(pack_file, scratch_pool));
1356   SVN_ERR(svn_io_file_close(pack_file, scratch_pool));
1357
1358   svn_pool_destroy(iterpool);
1359
1360   return SVN_NO_ERROR;
1361 }
1362
1363 svn_error_t *
1364 svn_fs_fs__pack_revprops_shard(const char *pack_file_dir,
1365                                const char *shard_path,
1366                                apr_int64_t shard,
1367                                int max_files_per_dir,
1368                                apr_int64_t max_pack_size,
1369                                int compression_level,
1370                                svn_boolean_t flush_to_disk,
1371                                svn_cancel_func_t cancel_func,
1372                                void *cancel_baton,
1373                                apr_pool_t *scratch_pool)
1374 {
1375   const char *manifest_file_path, *pack_filename = NULL;
1376   apr_file_t *manifest_file;
1377   svn_stream_t *manifest_stream;
1378   svn_revnum_t start_rev, end_rev, rev;
1379   apr_size_t total_size;
1380   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1381   apr_array_header_t *sizes;
1382
1383   /* Sanitize config file values. */
1384   apr_size_t max_size = (apr_size_t)MIN(MAX(max_pack_size, 1),
1385                                         SVN_MAX_OBJECT_SIZE);
1386
1387   /* Some useful paths. */
1388   manifest_file_path = svn_dirent_join(pack_file_dir, PATH_MANIFEST,
1389                                        scratch_pool);
1390
1391   /* Remove any existing pack file for this shard, since it is incomplete. */
1392   SVN_ERR(svn_io_remove_dir2(pack_file_dir, TRUE, cancel_func, cancel_baton,
1393                              scratch_pool));
1394
1395   /* Create the new directory and manifest file stream. */
1396   SVN_ERR(svn_io_dir_make(pack_file_dir, APR_OS_DEFAULT, scratch_pool));
1397
1398   SVN_ERR(svn_io_file_open(&manifest_file, manifest_file_path,
1399                            APR_WRITE | APR_BUFFERED | APR_CREATE | APR_EXCL,
1400                            APR_OS_DEFAULT, scratch_pool));
1401   manifest_stream = svn_stream_from_aprfile2(manifest_file, TRUE,
1402                                              scratch_pool);
1403
1404   /* revisions to handle. Special case: revision 0 */
1405   start_rev = (svn_revnum_t) (shard * max_files_per_dir);
1406   end_rev = (svn_revnum_t) ((shard + 1) * (max_files_per_dir) - 1);
1407   if (start_rev == 0)
1408     ++start_rev;
1409     /* Special special case: if max_files_per_dir is 1, then at this point
1410        start_rev == 1 and end_rev == 0 (!).  Fortunately, everything just
1411        works. */
1412
1413   /* initialize the revprop size info */
1414   sizes = apr_array_make(scratch_pool, max_files_per_dir, sizeof(apr_size_t));
1415   total_size = 2 * SVN_INT64_BUFFER_SIZE;
1416
1417   /* Iterate over the revisions in this shard, determine their size and
1418    * squashing them together into pack files. */
1419   for (rev = start_rev; rev <= end_rev; rev++)
1420     {
1421       apr_finfo_t finfo;
1422       const char *path;
1423
1424       svn_pool_clear(iterpool);
1425
1426       /* Get the size of the file. */
1427       path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
1428                              iterpool);
1429       SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_SIZE, iterpool));
1430
1431       /* If we already have started a pack file and this revprop cannot be
1432        * appended to it, write the previous pack file.  Note this overflow
1433        * check works because we enforced MAX_SIZE <= SVN_MAX_OBJECT_SIZE. */
1434       if (sizes->nelts != 0
1435           && (   finfo.size > max_size
1436               || total_size > max_size
1437               || SVN_INT64_BUFFER_SIZE + finfo.size > max_size - total_size))
1438         {
1439           SVN_ERR(svn_fs_fs__copy_revprops(pack_file_dir, pack_filename,
1440                                            shard_path, start_rev, rev-1,
1441                                            sizes, total_size,
1442                                            compression_level, flush_to_disk,
1443                                            cancel_func, cancel_baton,
1444                                            iterpool));
1445
1446           /* next pack file starts empty again */
1447           apr_array_clear(sizes);
1448           total_size = 2 * SVN_INT64_BUFFER_SIZE;
1449           start_rev = rev;
1450         }
1451
1452       /* Update the manifest. Allocate a file name for the current pack
1453        * file if it is a new one */
1454       if (sizes->nelts == 0)
1455         pack_filename = apr_psprintf(scratch_pool, "%ld.0", rev);
1456
1457       SVN_ERR(svn_stream_printf(manifest_stream, iterpool, "%s\n",
1458                                 pack_filename));
1459
1460       /* add to list of files to put into the current pack file */
1461       APR_ARRAY_PUSH(sizes, apr_size_t) = finfo.size;
1462       total_size += SVN_INT64_BUFFER_SIZE + finfo.size;
1463     }
1464
1465   /* write the last pack file */
1466   if (sizes->nelts != 0)
1467     SVN_ERR(svn_fs_fs__copy_revprops(pack_file_dir, pack_filename,
1468                                      shard_path, start_rev, rev-1,
1469                                      sizes, (apr_size_t)total_size,
1470                                      compression_level, flush_to_disk,
1471                                      cancel_func, cancel_baton, iterpool));
1472
1473   /* flush the manifest file to disk and update permissions */
1474   SVN_ERR(svn_stream_close(manifest_stream));
1475   if (flush_to_disk)
1476     SVN_ERR(svn_io_file_flush_to_disk(manifest_file, iterpool));
1477   SVN_ERR(svn_io_file_close(manifest_file, iterpool));
1478   SVN_ERR(svn_io_copy_perms(shard_path, pack_file_dir, iterpool));
1479
1480   svn_pool_destroy(iterpool);
1481
1482   return SVN_NO_ERROR;
1483 }
1484
1485 svn_error_t *
1486 svn_fs_fs__delete_revprops_shard(const char *shard_path,
1487                                  apr_int64_t shard,
1488                                  int max_files_per_dir,
1489                                  svn_cancel_func_t cancel_func,
1490                                  void *cancel_baton,
1491                                  apr_pool_t *scratch_pool)
1492 {
1493   if (shard == 0)
1494     {
1495       apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1496       int i;
1497
1498       /* delete all files except the one for revision 0 */
1499       for (i = 1; i < max_files_per_dir; ++i)
1500         {
1501           const char *path;
1502           svn_pool_clear(iterpool);
1503
1504           path = svn_dirent_join(shard_path,
1505                                  apr_psprintf(iterpool, "%d", i),
1506                                  iterpool);
1507           if (cancel_func)
1508             SVN_ERR(cancel_func(cancel_baton));
1509
1510           SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
1511         }
1512
1513       svn_pool_destroy(iterpool);
1514     }
1515   else
1516     SVN_ERR(svn_io_remove_dir2(shard_path, TRUE,
1517                                cancel_func, cancel_baton, scratch_pool));
1518
1519   return SVN_NO_ERROR;
1520 }
1521