]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_fs_x/revprops.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_fs_x / revprops.c
1 /* revprops.c --- everything needed to handle revprops in FSX
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 #include <apr_md5.h>
25
26 #include "svn_pools.h"
27 #include "svn_hash.h"
28 #include "svn_dirent_uri.h"
29
30 #include "fs_x.h"
31 #include "revprops.h"
32 #include "util.h"
33 #include "transaction.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 /* Give writing processes 10 seconds to replace an existing revprop
42    file with a new one. After that time, we assume that the writing
43    process got aborted and that we have re-read revprops. */
44 #define REVPROP_CHANGE_TIMEOUT (10 * 1000000)
45
46 /* In case of an inconsistent read, close the generation file, yield,
47    re-open and re-read.  This is the number of times we try this before
48    giving up. */
49 #define GENERATION_READ_RETRY_COUNT 100
50
51 /* Maximum size of the generation number file contents (including NUL). */
52 #define CHECKSUMMED_NUMBER_BUFFER_LEN \
53            (SVN_INT64_BUFFER_SIZE + 3 + APR_MD5_DIGESTSIZE * 2)
54
55
56 svn_error_t *
57 svn_fs_x__upgrade_pack_revprops(svn_fs_t *fs,
58                                 svn_fs_upgrade_notify_t notify_func,
59                                 void *notify_baton,
60                                 svn_cancel_func_t cancel_func,
61                                 void *cancel_baton,
62                                 apr_pool_t *scratch_pool)
63 {
64   svn_fs_x__data_t *ffd = fs->fsap_data;
65   const char *revprops_shard_path;
66   const char *revprops_pack_file_dir;
67   apr_int64_t shard;
68   apr_int64_t first_unpacked_shard
69     =  ffd->min_unpacked_rev / ffd->max_files_per_dir;
70
71   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
72   const char *revsprops_dir = svn_dirent_join(fs->path, PATH_REVPROPS_DIR,
73                                               scratch_pool);
74   int compression_level = ffd->compress_packed_revprops
75                            ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
76                            : SVN_DELTA_COMPRESSION_LEVEL_NONE;
77
78   /* first, pack all revprops shards to match the packed revision shards */
79   for (shard = 0; shard < first_unpacked_shard; ++shard)
80     {
81       svn_pool_clear(iterpool);
82
83       revprops_pack_file_dir = svn_dirent_join(revsprops_dir,
84                    apr_psprintf(iterpool,
85                                 "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
86                                 shard),
87                    iterpool);
88       revprops_shard_path = svn_dirent_join(revsprops_dir,
89                        apr_psprintf(iterpool, "%" APR_INT64_T_FMT, shard),
90                        iterpool);
91
92       SVN_ERR(svn_fs_x__pack_revprops_shard(revprops_pack_file_dir,
93                                       revprops_shard_path,
94                                       shard, ffd->max_files_per_dir,
95                                       (int)(0.9 * ffd->revprop_pack_size),
96                                       compression_level,
97                                       cancel_func, cancel_baton, iterpool));
98       if (notify_func)
99         SVN_ERR(notify_func(notify_baton, shard,
100                             svn_fs_upgrade_pack_revprops, iterpool));
101     }
102
103   svn_pool_destroy(iterpool);
104
105   return SVN_NO_ERROR;
106 }
107
108 svn_error_t *
109 svn_fs_x__upgrade_cleanup_pack_revprops(svn_fs_t *fs,
110                                         svn_fs_upgrade_notify_t notify_func,
111                                         void *notify_baton,
112                                         svn_cancel_func_t cancel_func,
113                                         void *cancel_baton,
114                                         apr_pool_t *scratch_pool)
115 {
116   svn_fs_x__data_t *ffd = fs->fsap_data;
117   const char *revprops_shard_path;
118   apr_int64_t shard;
119   apr_int64_t first_unpacked_shard
120     =  ffd->min_unpacked_rev / ffd->max_files_per_dir;
121
122   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
123   const char *revsprops_dir = svn_dirent_join(fs->path, PATH_REVPROPS_DIR,
124                                               scratch_pool);
125
126   /* delete the non-packed revprops shards afterwards */
127   for (shard = 0; shard < first_unpacked_shard; ++shard)
128     {
129       svn_pool_clear(iterpool);
130
131       revprops_shard_path = svn_dirent_join(revsprops_dir,
132                        apr_psprintf(iterpool, "%" APR_INT64_T_FMT, shard),
133                        iterpool);
134       SVN_ERR(svn_fs_x__delete_revprops_shard(revprops_shard_path,
135                                               shard, ffd->max_files_per_dir,
136                                               cancel_func, cancel_baton,
137                                               iterpool));
138       if (notify_func)
139         SVN_ERR(notify_func(notify_baton, shard,
140                             svn_fs_upgrade_cleanup_revprops, iterpool));
141     }
142
143   svn_pool_destroy(iterpool);
144
145   return SVN_NO_ERROR;
146 }
147
148 /* Revprop caching management.
149  *
150  * Mechanism:
151  * ----------
152  *
153  * Revprop caching needs to be activated and will be deactivated for the
154  * respective FS instance if the necessary infrastructure could not be
155  * initialized.  As long as no revprops are being read or changed, revprop
156  * caching imposes no overhead.
157  *
158  * When activated, we cache revprops using (revision, generation) pairs
159  * as keys with the generation being incremented upon every revprop change.
160  * Since the cache is process-local, the generation needs to be tracked
161  * for at least as long as the process lives but may be reset afterwards.
162  *
163  * We track the revprop generation in a persistent, unbuffered file that
164  * we may keep open for the lifetime of the svn_fs_t.  It is the OS'
165  * responsibility to provide us with the latest contents upon read.  To
166  * detect incomplete updates due to non-atomic reads, we put a MD5 checksum
167  * next to the actual generation number and verify that it matches.
168  *
169  * Since we cannot guarantee that the OS will provide us with up-to-date
170  * data buffers for open files, we re-open and re-read the file before
171  * modifying it.  This will prevent lost updates.
172  *
173  * A race condition exists between switching to the modified revprop data
174  * and bumping the generation number.  In particular, the process may crash
175  * just after switching to the new revprop data and before bumping the
176  * generation.  To be able to detect this scenario, we bump the generation
177  * twice per revprop change: once immediately before (creating an odd number)
178  * and once after the atomic switch (even generation).
179  *
180  * A writer holding the write lock can immediately assume a crashed writer
181  * in case of an odd generation or they would not have been able to acquire
182  * the lock.  A reader detecting an odd generation will use that number and
183  * be forced to re-read any revprop data - usually getting the new revprops
184  * already.  If the generation file modification timestamp is too old, the
185  * reader will assume a crashed writer, acquire the write lock and bump
186  * the generation if it is still odd.  So, for about REVPROP_CHANGE_TIMEOUT
187  * after the crash, reader caches may be stale.
188  */
189
190 /* If the revprop generation file in FS is open, close it.  This is a no-op
191  * if the file is not open.
192  */
193 static svn_error_t *
194 close_revprop_generation_file(svn_fs_t *fs,
195                               apr_pool_t *scratch_pool)
196 {
197   svn_fs_x__data_t *ffd = fs->fsap_data;
198   if (ffd->revprop_generation_file)
199     {
200       SVN_ERR(svn_io_file_close(ffd->revprop_generation_file, scratch_pool));
201       ffd->revprop_generation_file = NULL;
202     }
203
204   return SVN_NO_ERROR;
205 }
206
207 /* Make sure the revprop_generation member in FS is set.  If READ_ONLY is
208  * set, open the file w/o write permission if the file is not open yet.
209  * The file is kept open if it has sufficient rights (or more) but will be
210  * closed and re-opened if it provided insufficient access rights.
211  *
212  * Call only for repos that support revprop caching.
213  */
214 static svn_error_t *
215 open_revprop_generation_file(svn_fs_t *fs,
216                              svn_boolean_t read_only,
217                              apr_pool_t *scratch_pool)
218 {
219   svn_fs_x__data_t *ffd = fs->fsap_data;
220   apr_int32_t flags = read_only ? APR_READ : (APR_READ | APR_WRITE);
221
222   /* Close the current file handle if it has insufficient rights. */
223   if (   ffd->revprop_generation_file
224       && (apr_file_flags_get(ffd->revprop_generation_file) & flags) != flags)
225     SVN_ERR(close_revprop_generation_file(fs, scratch_pool));
226
227   /* If not open already, open with sufficient rights. */
228   if (ffd->revprop_generation_file == NULL)
229     {
230       const char *path = svn_fs_x__path_revprop_generation(fs, scratch_pool);
231       SVN_ERR(svn_io_file_open(&ffd->revprop_generation_file, path,
232                                flags, APR_OS_DEFAULT, fs->pool));
233     }
234
235   return SVN_NO_ERROR;
236 }
237
238 /* Return the textual representation of NUMBER and its checksum in *BUFFER.
239  */
240 static svn_error_t *
241 checkedsummed_number(svn_stringbuf_t **buffer,
242                      apr_int64_t number,
243                      apr_pool_t *result_pool,
244                      apr_pool_t *scratch_pool)
245 {
246   svn_checksum_t *checksum;
247   const char *digest;
248
249   char str[SVN_INT64_BUFFER_SIZE];
250   apr_size_t len = svn__i64toa(str, number);
251   str[len] = 0;
252
253   SVN_ERR(svn_checksum(&checksum, svn_checksum_md5, str, len, scratch_pool));
254   digest = svn_checksum_to_cstring_display(checksum, scratch_pool);
255
256   *buffer = svn_stringbuf_createf(result_pool, "%s %s\n", digest, str);
257
258   return SVN_NO_ERROR;
259 }
260
261 /* Extract the generation number from the text BUFFER of LEN bytes and
262  * verify it against the checksum in the same BUFFER.  If they match, return
263  * the generation in *NUMBER.  Otherwise, return an error.
264  * BUFFER does not need to be NUL-terminated.
265  */
266 static svn_error_t *
267 verify_extract_number(apr_int64_t *number,
268                       const char *buffer,
269                       apr_size_t len,
270                       apr_pool_t *scratch_pool)
271 {
272   const char *digest_end = strchr(buffer, ' ');
273
274   /* Does the buffer even contain checksum _and_ number? */
275   if (digest_end != NULL)
276     {
277       svn_checksum_t *expected;
278       svn_checksum_t *actual;
279
280       SVN_ERR(svn_checksum_parse_hex(&expected, svn_checksum_md5, buffer,
281                                      scratch_pool));
282       SVN_ERR(svn_checksum(&actual, svn_checksum_md5, digest_end + 1,
283                            (buffer + len) - (digest_end + 1), scratch_pool));
284
285       if (svn_checksum_match(expected, actual))
286         return svn_error_trace(svn_cstring_atoi64(number, digest_end + 1));
287     }
288
289   /* Incomplete buffer or not a match. */
290   return svn_error_create(SVN_ERR_FS_INVALID_GENERATION, NULL,
291                           _("Invalid generation number data."));
292 }
293
294 /* Read revprop generation as stored on disk for repository FS. The result is
295  * returned in *CURRENT.  Call only for repos that support revprop caching.
296  */
297 static svn_error_t *
298 read_revprop_generation_file(apr_int64_t *current,
299                              svn_fs_t *fs,
300                              apr_pool_t *scratch_pool)
301 {
302   svn_fs_x__data_t *ffd = fs->fsap_data;
303   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
304   char buf[CHECKSUMMED_NUMBER_BUFFER_LEN];
305   apr_size_t len;
306   apr_off_t offset = 0;
307   int i;
308   svn_error_t *err = SVN_NO_ERROR;
309
310   /* Retry in case of incomplete file buffer updates. */
311   for (i = 0; i < GENERATION_READ_RETRY_COUNT; ++i)
312     {
313       svn_error_clear(err);
314       svn_pool_clear(iterpool);
315
316       /* If we can't even access the data, things are very wrong.
317        * Don't retry in that case.
318        */
319       SVN_ERR(open_revprop_generation_file(fs, TRUE, iterpool));
320       SVN_ERR(svn_io_file_seek(ffd->revprop_generation_file, APR_SET, &offset,
321                               iterpool));
322
323       len = sizeof(buf);
324       SVN_ERR(svn_io_read_length_line(ffd->revprop_generation_file, buf, &len,
325                                       iterpool));
326
327       /* Some data has been read.  It will most likely be complete and
328        * consistent.  Extract and verify anyway. */
329       err = verify_extract_number(current, buf, len, iterpool);
330       if (!err)
331         break;
332
333       /* Got unlucky and data was invalid.  Retry. */
334       SVN_ERR(close_revprop_generation_file(fs, iterpool));
335
336 #if APR_HAS_THREADS
337       apr_thread_yield();
338 #else
339       apr_sleep(0);
340 #endif
341     }
342
343   svn_pool_destroy(iterpool);
344
345   /* If we had to give up, propagate the error. */
346   return svn_error_trace(err);
347 }
348
349 /* Write the CURRENT revprop generation to disk for repository FS.
350  * Call only for repos that support revprop caching.
351  */
352 static svn_error_t *
353 write_revprop_generation_file(svn_fs_t *fs,
354                               apr_int64_t current,
355                               apr_pool_t *scratch_pool)
356 {
357   svn_fs_x__data_t *ffd = fs->fsap_data;
358   svn_stringbuf_t *buffer;
359   apr_off_t offset = 0;
360
361   SVN_ERR(checkedsummed_number(&buffer, current, scratch_pool, scratch_pool));
362
363   SVN_ERR(open_revprop_generation_file(fs, FALSE, scratch_pool));
364   SVN_ERR(svn_io_file_seek(ffd->revprop_generation_file, APR_SET, &offset,
365                            scratch_pool));
366   SVN_ERR(svn_io_file_write_full(ffd->revprop_generation_file, buffer->data,
367                                  buffer->len, NULL, scratch_pool));
368   SVN_ERR(svn_io_file_flush_to_disk(ffd->revprop_generation_file,
369                                     scratch_pool));
370
371   return SVN_NO_ERROR;
372 }
373
374 svn_error_t *
375 svn_fs_x__reset_revprop_generation_file(svn_fs_t *fs,
376                                         apr_pool_t *scratch_pool)
377 {
378   const char *path = svn_fs_x__path_revprop_generation(fs, scratch_pool);
379   svn_stringbuf_t *buffer;
380
381   /* Unconditionally close the revprop generation file.
382    * Don't care about FS formats. This ensures consistent internal state. */
383   SVN_ERR(close_revprop_generation_file(fs, scratch_pool));
384
385   /* Unconditionally remove any old revprop generation file.
386    * Don't care about FS formats.  This ensures consistent on-disk state
387    * for old format repositories. */
388   SVN_ERR(svn_io_remove_file2(path, TRUE, scratch_pool));
389
390   /* Write the initial revprop generation file contents, if supported by
391    * the current format.  This ensures consistent on-disk state for new
392    * format repositories. */
393   SVN_ERR(checkedsummed_number(&buffer, 0, scratch_pool, scratch_pool));
394   SVN_ERR(svn_io_write_atomic(path, buffer->data, buffer->len, NULL,
395                               scratch_pool));
396
397   /* ffd->revprop_generation_file will be re-opened on demand. */
398
399   return SVN_NO_ERROR;
400 }
401
402 /* Create an error object with the given MESSAGE and pass it to the
403    WARNING member of FS. Clears UNDERLYING_ERR. */
404 static void
405 log_revprop_cache_init_warning(svn_fs_t *fs,
406                                svn_error_t *underlying_err,
407                                const char *message,
408                                apr_pool_t *scratch_pool)
409 {
410   svn_error_t *err = svn_error_createf(
411                        SVN_ERR_FS_REVPROP_CACHE_INIT_FAILURE,
412                        underlying_err, message,
413                        svn_dirent_local_style(fs->path, scratch_pool));
414
415   if (fs->warning)
416     (fs->warning)(fs->warning_baton, err);
417
418   svn_error_clear(err);
419 }
420
421 /* Test whether revprop cache and necessary infrastructure are
422    available in FS. */
423 static svn_boolean_t
424 has_revprop_cache(svn_fs_t *fs,
425                   apr_pool_t *scratch_pool)
426 {
427   svn_fs_x__data_t *ffd = fs->fsap_data;
428   svn_error_t *error;
429
430   /* is the cache (still) enabled? */
431   if (ffd->revprop_cache == NULL)
432     return FALSE;
433
434   /* try initialize our file-backed infrastructure */
435   error = open_revprop_generation_file(fs, TRUE, scratch_pool);
436   if (error)
437     {
438       /* failure -> disable revprop cache for good */
439
440       ffd->revprop_cache = NULL;
441       log_revprop_cache_init_warning(fs, error,
442                                      "Revprop caching for '%s' disabled "
443                                      "because infrastructure for revprop "
444                                      "caching failed to initialize.",
445                                      scratch_pool);
446
447       return FALSE;
448     }
449
450   return TRUE;
451 }
452
453 /* Baton structure for revprop_generation_fixup. */
454 typedef struct revprop_generation_fixup_t
455 {
456   /* revprop generation to read */
457   apr_int64_t *generation;
458
459   /* file system context */
460   svn_fs_t *fs;
461 } revprop_generation_upgrade_t;
462
463 /* If the revprop generation has an odd value, it means the original writer
464    of the revprop got killed. We don't know whether that process as able
465    to change the revprop data but we assume that it was. Therefore, we
466    increase the generation in that case to basically invalidate everyone's
467    cache content.
468    Execute this only while holding the write lock to the repo in baton->FFD.
469  */
470 static svn_error_t *
471 revprop_generation_fixup(void *void_baton,
472                          apr_pool_t *scratch_pool)
473 {
474   revprop_generation_upgrade_t *baton = void_baton;
475   svn_fs_x__data_t *ffd = baton->fs->fsap_data;
476   assert(ffd->has_write_lock);
477
478   /* Make sure we don't operate on stale OS buffers. */
479   SVN_ERR(close_revprop_generation_file(baton->fs, scratch_pool));
480
481   /* Maybe, either the original revprop writer or some other reader has
482      already corrected / bumped the revprop generation.  Thus, we need
483      to read it again.  However, we will now be the only ones changing
484      the file contents due to us holding the write lock. */
485   SVN_ERR(read_revprop_generation_file(baton->generation, baton->fs,
486                                        scratch_pool));
487
488   /* Cause everyone to re-read revprops upon their next access, if the
489      last revprop write did not complete properly. */
490   if (*baton->generation % 2)
491     {
492       ++*baton->generation;
493       SVN_ERR(write_revprop_generation_file(baton->fs,
494                                             *baton->generation,
495                                             scratch_pool));
496     }
497
498   return SVN_NO_ERROR;
499 }
500
501 /* Read the current revprop generation and return it in *GENERATION.
502    Also, detect aborted / crashed writers and recover from that.
503    Use the access object in FS to set the shared mem values. */
504 static svn_error_t *
505 read_revprop_generation(apr_int64_t *generation,
506                         svn_fs_t *fs,
507                         apr_pool_t *scratch_pool)
508 {
509   apr_int64_t current = 0;
510   svn_fs_x__data_t *ffd = fs->fsap_data;
511
512   /* read the current revprop generation number */
513   SVN_ERR(read_revprop_generation_file(&current, fs, scratch_pool));
514
515   /* is an unfinished revprop write under the way? */
516   if (current % 2)
517     {
518       svn_boolean_t timeout = FALSE;
519
520       /* Has the writer process been aborted?
521        * Either by timeout or by us being the writer now.
522        */
523       if (!ffd->has_write_lock)
524         {
525           apr_time_t mtime;
526           SVN_ERR(svn_io_file_affected_time(&mtime,
527                         svn_fs_x__path_revprop_generation(fs, scratch_pool),
528                         scratch_pool));
529           timeout = apr_time_now() > mtime + REVPROP_CHANGE_TIMEOUT;
530         }
531
532       if (ffd->has_write_lock || timeout)
533         {
534           revprop_generation_upgrade_t baton;
535           baton.generation = &current;
536           baton.fs = fs;
537
538           /* Ensure that the original writer process no longer exists by
539            * acquiring the write lock to this repository.  Then, fix up
540            * the revprop generation.
541            */
542           if (ffd->has_write_lock)
543             SVN_ERR(revprop_generation_fixup(&baton, scratch_pool));
544           else
545             SVN_ERR(svn_fs_x__with_write_lock(fs, revprop_generation_fixup,
546                                               &baton, scratch_pool));
547         }
548     }
549
550   /* return the value we just got */
551   *generation = current;
552   return SVN_NO_ERROR;
553 }
554
555 /* Set the revprop generation in FS to the next odd number to indicate
556    that there is a revprop write process under way.  Return that value
557    in *GENERATION.  If the change times out, readers shall recover from
558    that state & re-read revprops.
559    This is a no-op for repo formats that don't support revprop caching. */
560 static svn_error_t *
561 begin_revprop_change(apr_int64_t *generation,
562                      svn_fs_t *fs,
563                      apr_pool_t *scratch_pool)
564 {
565   svn_fs_x__data_t *ffd = fs->fsap_data;
566   SVN_ERR_ASSERT(ffd->has_write_lock);
567
568   /* Close and re-open to make sure we read the latest data. */
569   SVN_ERR(close_revprop_generation_file(fs, scratch_pool));
570   SVN_ERR(open_revprop_generation_file(fs, FALSE, scratch_pool));
571
572   /* Set the revprop generation to an odd value to indicate
573    * that a write is in progress.
574    */
575   SVN_ERR(read_revprop_generation(generation, fs, scratch_pool));
576   ++*generation;
577   SVN_ERR(write_revprop_generation_file(fs, *generation, scratch_pool));
578
579   return SVN_NO_ERROR;
580 }
581
582 /* Set the revprop generation in FS to the next even generation after
583    the odd value in GENERATION to indicate that
584    a) readers shall re-read revprops, and
585    b) the write process has been completed (no recovery required).
586    This is a no-op for repo formats that don't support revprop caching. */
587 static svn_error_t *
588 end_revprop_change(svn_fs_t *fs,
589                    apr_int64_t generation,
590                    apr_pool_t *scratch_pool)
591 {
592   svn_fs_x__data_t *ffd = fs->fsap_data;
593   SVN_ERR_ASSERT(ffd->has_write_lock);
594   SVN_ERR_ASSERT(generation % 2);
595
596   /* Set the revprop generation to an even value to indicate
597    * that a write has been completed.  Since we held the write
598    * lock, nobody else could have updated the file contents.
599    */
600   SVN_ERR(write_revprop_generation_file(fs, generation + 1, scratch_pool));
601
602   return SVN_NO_ERROR;
603 }
604
605 /* Container for all data required to access the packed revprop file
606  * for a given REVISION.  This structure will be filled incrementally
607  * by read_pack_revprops() its sub-routines.
608  */
609 typedef struct packed_revprops_t
610 {
611   /* revision number to read (not necessarily the first in the pack) */
612   svn_revnum_t revision;
613
614   /* current revprop generation. Used when populating the revprop cache */
615   apr_int64_t generation;
616
617   /* the actual revision properties */
618   apr_hash_t *properties;
619
620   /* their size when serialized to a single string
621    * (as found in PACKED_REVPROPS) */
622   apr_size_t serialized_size;
623
624
625   /* name of the pack file (without folder path) */
626   const char *filename;
627
628   /* packed shard folder path */
629   const char *folder;
630
631   /* sum of values in SIZES */
632   apr_size_t total_size;
633
634   /* first revision in the pack (>= MANIFEST_START) */
635   svn_revnum_t start_revision;
636
637   /* size of the revprops in PACKED_REVPROPS */
638   apr_array_header_t *sizes;
639
640   /* offset of the revprops in PACKED_REVPROPS */
641   apr_array_header_t *offsets;
642
643
644   /* concatenation of the serialized representation of all revprops
645    * in the pack, i.e. the pack content without header and compression */
646   svn_stringbuf_t *packed_revprops;
647
648   /* First revision covered by MANIFEST.
649    * Will equal the shard start revision or 1, for the 1st shard. */
650   svn_revnum_t manifest_start;
651
652   /* content of the manifest.
653    * Maps long(rev - MANIFEST_START) to const char* pack file name */
654   apr_array_header_t *manifest;
655 } packed_revprops_t;
656
657 /* Parse the serialized revprops in CONTENT and return them in *PROPERTIES.
658  * Also, put them into the revprop cache, if activated, for future use.
659  * Three more parameters are being used to update the revprop cache: FS is
660  * our file system, the revprops belong to REVISION and the global revprop
661  * GENERATION is used as well.
662  *
663  * The returned hash will be allocated in RESULT_POOL, SCRATCH_POOL is
664  * being used for temporary allocations.
665  */
666 static svn_error_t *
667 parse_revprop(apr_hash_t **properties,
668               svn_fs_t *fs,
669               svn_revnum_t revision,
670               apr_int64_t generation,
671               svn_string_t *content,
672               apr_pool_t *result_pool,
673               apr_pool_t *scratch_pool)
674 {
675   svn_stream_t *stream = svn_stream_from_string(content, scratch_pool);
676   *properties = apr_hash_make(result_pool);
677
678   SVN_ERR(svn_hash_read2(*properties, stream, SVN_HASH_TERMINATOR,
679                          result_pool));
680   if (has_revprop_cache(fs, scratch_pool))
681     {
682       svn_fs_x__data_t *ffd = fs->fsap_data;
683       svn_fs_x__pair_cache_key_t key = { 0 };
684
685       key.revision = revision;
686       key.second = generation;
687       SVN_ERR(svn_cache__set(ffd->revprop_cache, &key, *properties,
688                              scratch_pool));
689     }
690
691   return SVN_NO_ERROR;
692 }
693
694 /* Read the non-packed revprops for revision REV in FS, put them into the
695  * revprop cache if activated and return them in *PROPERTIES.  GENERATION
696  * is the current revprop generation.
697  *
698  * If the data could not be read due to an otherwise recoverable error,
699  * leave *PROPERTIES unchanged. No error will be returned in that case.
700  *
701  * Allocate *PROPERTIES in RESULT_POOL and temporaries in SCRATCH_POOL.
702  */
703 static svn_error_t *
704 read_non_packed_revprop(apr_hash_t **properties,
705                         svn_fs_t *fs,
706                         svn_revnum_t rev,
707                         apr_int64_t generation,
708                         apr_pool_t *result_pool,
709                         apr_pool_t *scratch_pool)
710 {
711   svn_stringbuf_t *content = NULL;
712   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
713   svn_boolean_t missing = FALSE;
714   int i;
715
716   for (i = 0;
717        i < SVN_FS_X__RECOVERABLE_RETRY_COUNT && !missing && !content;
718        ++i)
719     {
720       svn_pool_clear(iterpool);
721       SVN_ERR(svn_fs_x__try_stringbuf_from_file(&content,
722                                   &missing,
723                                   svn_fs_x__path_revprops(fs, rev, iterpool),
724                                   i + 1 < SVN_FS_X__RECOVERABLE_RETRY_COUNT,
725                                   iterpool));
726     }
727
728   if (content)
729     SVN_ERR(parse_revprop(properties, fs, rev, generation,
730                           svn_stringbuf__morph_into_string(content),
731                           result_pool, iterpool));
732
733   svn_pool_clear(iterpool);
734
735   return SVN_NO_ERROR;
736 }
737
738 /* Return the minimum length of any packed revprop file name in REVPROPS. */
739 static apr_size_t
740 get_min_filename_len(packed_revprops_t *revprops)
741 {
742   char number_buffer[SVN_INT64_BUFFER_SIZE];
743
744   /* The revprop filenames have the format <REV>.<COUNT> - with <REV> being
745    * at least the first rev in the shard and <COUNT> having at least one
746    * digit.  Thus, the minimum is 2 + #decimal places in the start rev.
747    */
748   return svn__i64toa(number_buffer, revprops->manifest_start) + 2;
749 }
750
751 /* Given FS and REVPROPS->REVISION, fill the FILENAME, FOLDER and MANIFEST
752  * members. Use RESULT_POOL for allocating results and SCRATCH_POOL for
753  * temporaries.
754  */
755 static svn_error_t *
756 get_revprop_packname(svn_fs_t *fs,
757                      packed_revprops_t *revprops,
758                      apr_pool_t *result_pool,
759                      apr_pool_t *scratch_pool)
760 {
761   svn_fs_x__data_t *ffd = fs->fsap_data;
762   svn_stringbuf_t *content = NULL;
763   const char *manifest_file_path;
764   int idx, rev_count;
765   char *buffer, *buffer_end;
766   const char **filenames, **filenames_end;
767   apr_size_t min_filename_len;
768
769   /* Determine the dimensions. Rev 0 is excluded from the first shard. */
770   rev_count = ffd->max_files_per_dir;
771   revprops->manifest_start
772     = revprops->revision - (revprops->revision % rev_count);
773   if (revprops->manifest_start == 0)
774     {
775       ++revprops->manifest_start;
776       --rev_count;
777     }
778
779   revprops->manifest = apr_array_make(result_pool, rev_count,
780                                       sizeof(const char*));
781
782   /* No line in the file can be less than this number of chars long. */
783   min_filename_len = get_min_filename_len(revprops);
784
785   /* Read the content of the manifest file */
786   revprops->folder
787     = svn_fs_x__path_revprops_pack_shard(fs, revprops->revision, result_pool);
788   manifest_file_path = svn_dirent_join(revprops->folder, PATH_MANIFEST,
789                                        result_pool);
790
791   SVN_ERR(svn_fs_x__read_content(&content, manifest_file_path, result_pool));
792
793   /* There CONTENT must have a certain minimal size and there no
794    * unterminated lines at the end of the file.  Both guarantees also
795    * simplify the parser loop below.
796    */
797   if (   content->len < rev_count * (min_filename_len + 1)
798       || content->data[content->len - 1] != '\n')
799     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
800                              _("Packed revprop manifest for r%ld not "
801                                "properly terminated"), revprops->revision);
802
803   /* Chop (parse) the manifest CONTENT into filenames, one per line.
804    * We only have to replace all newlines with NUL and add all line
805    * starts to REVPROPS->MANIFEST.
806    *
807    * There must be exactly REV_COUNT lines and that is the number of
808    * lines we parse from BUFFER to FILENAMES.  Set the end pointer for
809    * the source BUFFER such that BUFFER+MIN_FILENAME_LEN is still valid
810    * BUFFER_END is always valid due to CONTENT->LEN > MIN_FILENAME_LEN.
811    *
812    * Please note that this loop is performance critical for e.g. 'svn log'.
813    * It is run 1000x per revprop access, i.e. per revision and about
814    * 50 million times per sec (and CPU core).
815    */
816   for (filenames = (const char **)revprops->manifest->elts,
817        filenames_end = filenames + rev_count,
818        buffer = content->data,
819        buffer_end = buffer + content->len - min_filename_len;
820        (filenames < filenames_end) && (buffer < buffer_end);
821        ++filenames)
822     {
823       /* BUFFER always points to the start of the next line / filename. */
824       *filenames = buffer;
825
826       /* Find the next EOL.  This is guaranteed to stay within the CONTENT
827        * buffer because we left enough room after BUFFER_END and we know
828        * we will always see a newline as the last non-NUL char. */
829       buffer += min_filename_len;
830       while (*buffer != '\n')
831         ++buffer;
832
833       /* Found EOL.  Turn it into the filename terminator and move BUFFER
834        * to the start of the next line or CONTENT buffer end. */
835       *buffer = '\0';
836       ++buffer;
837     }
838
839   /* We must have reached the end of both buffers. */
840   if (buffer < content->data + content->len)
841     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
842                              _("Packed revprop manifest for r%ld "
843                                "has too many entries"), revprops->revision);
844
845   if (filenames < filenames_end)
846     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
847                              _("Packed revprop manifest for r%ld "
848                                "has too few entries"), revprops->revision);
849
850   /* The target array has now exactly one entry per revision. */
851   revprops->manifest->nelts = rev_count;
852
853   /* Now get the file name */
854   idx = (int)(revprops->revision - revprops->manifest_start);
855   revprops->filename = APR_ARRAY_IDX(revprops->manifest, idx, const char*);
856
857   return SVN_NO_ERROR;
858 }
859
860 /* Return TRUE, if revision R1 and R2 refer to the same shard in FS.
861  */
862 static svn_boolean_t
863 same_shard(svn_fs_t *fs,
864            svn_revnum_t r1,
865            svn_revnum_t r2)
866 {
867   svn_fs_x__data_t *ffd = fs->fsap_data;
868   return (r1 / ffd->max_files_per_dir) == (r2 / ffd->max_files_per_dir);
869 }
870
871 /* Given FS and the full packed file content in REVPROPS->PACKED_REVPROPS,
872  * fill the START_REVISION member, and make PACKED_REVPROPS point to the
873  * first serialized revprop.  If READ_ALL is set, initialize the SIZES
874  * and OFFSETS members as well.
875  *
876  * Parse the revprops for REVPROPS->REVISION and set the PROPERTIES as
877  * well as the SERIALIZED_SIZE member.  If revprop caching has been
878  * enabled, parse all revprops in the pack and cache them.
879  */
880 static svn_error_t *
881 parse_packed_revprops(svn_fs_t *fs,
882                       packed_revprops_t *revprops,
883                       svn_boolean_t read_all,
884                       apr_pool_t *result_pool,
885                       apr_pool_t *scratch_pool)
886 {
887   svn_stream_t *stream;
888   apr_int64_t first_rev, count, i;
889   apr_off_t offset;
890   const char *header_end;
891   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
892   svn_boolean_t cache_all = has_revprop_cache(fs, scratch_pool);
893
894   /* decompress (even if the data is only "stored", there is still a
895    * length header to remove) */
896   svn_stringbuf_t *compressed = revprops->packed_revprops;
897   svn_stringbuf_t *uncompressed = svn_stringbuf_create_empty(result_pool);
898   SVN_ERR(svn__decompress(compressed, uncompressed, APR_SIZE_MAX));
899
900   /* read first revision number and number of revisions in the pack */
901   stream = svn_stream_from_stringbuf(uncompressed, scratch_pool);
902   SVN_ERR(svn_fs_x__read_number_from_stream(&first_rev, NULL, stream,
903                                             iterpool));
904   SVN_ERR(svn_fs_x__read_number_from_stream(&count, NULL, stream, iterpool));
905
906   /* Check revision range for validity. */
907   if (   !same_shard(fs, revprops->revision, first_rev)
908       || !same_shard(fs, revprops->revision, first_rev + count - 1)
909       || count < 1)
910     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
911                              _("Revprop pack for revision r%ld"
912                                " contains revprops for r%ld .. r%ld"),
913                              revprops->revision,
914                              (svn_revnum_t)first_rev,
915                              (svn_revnum_t)(first_rev + count -1));
916
917   /* Since start & end are in the same shard, it is enough to just test
918    * the FIRST_REV for being actually packed.  That will also cover the
919    * special case of rev 0 never being packed. */
920   if (!svn_fs_x__is_packed_revprop(fs, first_rev))
921     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
922                              _("Revprop pack for revision r%ld"
923                                " starts at non-packed revisions r%ld"),
924                              revprops->revision, (svn_revnum_t)first_rev);
925
926   /* make PACKED_REVPROPS point to the first char after the header.
927    * This is where the serialized revprops are. */
928   header_end = strstr(uncompressed->data, "\n\n");
929   if (header_end == NULL)
930     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
931                             _("Header end not found"));
932
933   offset = header_end - uncompressed->data + 2;
934
935   revprops->packed_revprops = svn_stringbuf_create_empty(result_pool);
936   revprops->packed_revprops->data = uncompressed->data + offset;
937   revprops->packed_revprops->len = (apr_size_t)(uncompressed->len - offset);
938   revprops->packed_revprops->blocksize = (apr_size_t)(uncompressed->blocksize - offset);
939
940   /* STREAM still points to the first entry in the sizes list. */
941   revprops->start_revision = (svn_revnum_t)first_rev;
942   if (read_all)
943     {
944       /* Init / construct REVPROPS members. */
945       revprops->sizes = apr_array_make(result_pool, (int)count,
946                                        sizeof(offset));
947       revprops->offsets = apr_array_make(result_pool, (int)count,
948                                          sizeof(offset));
949     }
950
951   /* Now parse, revision by revision, the size and content of each
952    * revisions' revprops. */
953   for (i = 0, offset = 0, revprops->total_size = 0; i < count; ++i)
954     {
955       apr_int64_t size;
956       svn_string_t serialized;
957       svn_revnum_t revision = (svn_revnum_t)(first_rev + i);
958       svn_pool_clear(iterpool);
959
960       /* read & check the serialized size */
961       SVN_ERR(svn_fs_x__read_number_from_stream(&size, NULL, stream,
962                                                 iterpool));
963       if (size + offset > (apr_int64_t)revprops->packed_revprops->len)
964         return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
965                         _("Packed revprop size exceeds pack file size"));
966
967       /* Parse this revprops list, if necessary */
968       serialized.data = revprops->packed_revprops->data + offset;
969       serialized.len = (apr_size_t)size;
970
971       if (revision == revprops->revision)
972         {
973           /* Parse (and possibly cache) the one revprop list we care about. */
974           SVN_ERR(parse_revprop(&revprops->properties, fs, revision,
975                                 revprops->generation, &serialized,
976                                 result_pool, iterpool));
977           revprops->serialized_size = serialized.len;
978
979           /* If we only wanted the revprops for REVISION then we are done. */
980           if (!read_all && !cache_all)
981             break;
982         }
983       else if (cache_all)
984         {
985           /* Parse and cache all other revprop lists. */
986           apr_hash_t *properties;
987           SVN_ERR(parse_revprop(&properties, fs, revision,
988                                 revprops->generation, &serialized,
989                                 iterpool, iterpool));
990         }
991
992       if (read_all)
993         {
994           /* fill REVPROPS data structures */
995           APR_ARRAY_PUSH(revprops->sizes, apr_off_t) = serialized.len;
996           APR_ARRAY_PUSH(revprops->offsets, apr_off_t) = offset;
997         }
998       revprops->total_size += serialized.len;
999
1000       offset += serialized.len;
1001     }
1002
1003   return SVN_NO_ERROR;
1004 }
1005
1006 /* In filesystem FS, read the packed revprops for revision REV into
1007  * *REVPROPS.  Use GENERATION to populate the revprop cache, if enabled.
1008  * If you want to modify revprop contents / update REVPROPS, READ_ALL
1009  * must be set.  Otherwise, only the properties of REV are being provided.
1010  *
1011  * Allocate *PROPERTIES in RESULT_POOL and temporaries in SCRATCH_POOL.
1012  */
1013 static svn_error_t *
1014 read_pack_revprop(packed_revprops_t **revprops,
1015                   svn_fs_t *fs,
1016                   svn_revnum_t rev,
1017                   apr_int64_t generation,
1018                   svn_boolean_t read_all,
1019                   apr_pool_t *result_pool,
1020                   apr_pool_t *scratch_pool)
1021 {
1022   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1023   svn_boolean_t missing = FALSE;
1024   svn_error_t *err;
1025   packed_revprops_t *result;
1026   int i;
1027
1028   /* someone insisted that REV is packed. Double-check if necessary */
1029   if (!svn_fs_x__is_packed_revprop(fs, rev))
1030      SVN_ERR(svn_fs_x__update_min_unpacked_rev(fs, iterpool));
1031
1032   if (!svn_fs_x__is_packed_revprop(fs, rev))
1033     return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1034                               _("No such packed revision %ld"), rev);
1035
1036   /* initialize the result data structure */
1037   result = apr_pcalloc(result_pool, sizeof(*result));
1038   result->revision = rev;
1039   result->generation = generation;
1040
1041   /* try to read the packed revprops. This may require retries if we have
1042    * concurrent writers. */
1043   for (i = 0;
1044        i < SVN_FS_X__RECOVERABLE_RETRY_COUNT && !result->packed_revprops;
1045        ++i)
1046     {
1047       const char *file_path;
1048       svn_pool_clear(iterpool);
1049
1050       /* there might have been concurrent writes.
1051        * Re-read the manifest and the pack file.
1052        */
1053       SVN_ERR(get_revprop_packname(fs, result, result_pool, iterpool));
1054       file_path  = svn_dirent_join(result->folder,
1055                                    result->filename,
1056                                    iterpool);
1057       SVN_ERR(svn_fs_x__try_stringbuf_from_file(&result->packed_revprops,
1058                                 &missing,
1059                                 file_path,
1060                                 i + 1 < SVN_FS_X__RECOVERABLE_RETRY_COUNT,
1061                                 result_pool));
1062
1063       /* If we could not find the file, there was a write.
1064        * So, we should refresh our revprop generation info as well such
1065        * that others may find data we will put into the cache.  They would
1066        * consider it outdated, otherwise.
1067        */
1068       if (missing && has_revprop_cache(fs, iterpool))
1069         SVN_ERR(read_revprop_generation(&result->generation, fs, iterpool));
1070     }
1071
1072   /* the file content should be available now */
1073   if (!result->packed_revprops)
1074     return svn_error_createf(SVN_ERR_FS_PACKED_REVPROP_READ_FAILURE, NULL,
1075                   _("Failed to read revprop pack file for r%ld"), rev);
1076
1077   /* parse it. RESULT will be complete afterwards. */
1078   err = parse_packed_revprops(fs, result, read_all, result_pool, iterpool);
1079   svn_pool_destroy(iterpool);
1080   if (err)
1081     return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
1082                   _("Revprop pack file for r%ld is corrupt"), rev);
1083
1084   *revprops = result;
1085
1086   return SVN_NO_ERROR;
1087 }
1088
1089 /* Read the revprops for revision REV in FS and return them in *PROPERTIES_P.
1090  *
1091  * Allocations will be done in POOL.
1092  */
1093 svn_error_t *
1094 svn_fs_x__get_revision_proplist(apr_hash_t **proplist_p,
1095                                 svn_fs_t *fs,
1096                                 svn_revnum_t rev,
1097                                 svn_boolean_t bypass_cache,
1098                                 apr_pool_t *result_pool,
1099                                 apr_pool_t *scratch_pool)
1100 {
1101   svn_fs_x__data_t *ffd = fs->fsap_data;
1102   apr_int64_t generation = 0;
1103
1104   /* not found, yet */
1105   *proplist_p = NULL;
1106
1107   /* should they be available at all? */
1108   SVN_ERR(svn_fs_x__ensure_revision_exists(rev, fs, scratch_pool));
1109
1110   /* Try cache lookup first. */
1111   if (!bypass_cache && has_revprop_cache(fs, scratch_pool))
1112     {
1113       svn_boolean_t is_cached;
1114       svn_fs_x__pair_cache_key_t key = { 0 };
1115
1116       SVN_ERR(read_revprop_generation(&generation, fs, scratch_pool));
1117
1118       key.revision = rev;
1119       key.second = generation;
1120       SVN_ERR(svn_cache__get((void **) proplist_p, &is_cached,
1121                              ffd->revprop_cache, &key, result_pool));
1122       if (is_cached)
1123         return SVN_NO_ERROR;
1124     }
1125
1126   /* if REV had not been packed when we began, try reading it from the
1127    * non-packed shard.  If that fails, we will fall through to packed
1128    * shard reads. */
1129   if (!svn_fs_x__is_packed_revprop(fs, rev))
1130     {
1131       svn_error_t *err = read_non_packed_revprop(proplist_p, fs, rev,
1132                                                  generation, result_pool,
1133                                                  scratch_pool);
1134       if (err)
1135         {
1136           if (!APR_STATUS_IS_ENOENT(err->apr_err))
1137             return svn_error_trace(err);
1138
1139           svn_error_clear(err);
1140           *proplist_p = NULL; /* in case read_non_packed_revprop changed it */
1141         }
1142     }
1143
1144   /* if revprop packing is available and we have not read the revprops, yet,
1145    * try reading them from a packed shard.  If that fails, REV is most
1146    * likely invalid (or its revprops highly contested). */
1147   if (!*proplist_p)
1148     {
1149       packed_revprops_t *revprops;
1150       SVN_ERR(read_pack_revprop(&revprops, fs, rev, generation, FALSE,
1151                                 result_pool, scratch_pool));
1152       *proplist_p = revprops->properties;
1153     }
1154
1155   /* The revprops should have been there. Did we get them? */
1156   if (!*proplist_p)
1157     return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1158                              _("Could not read revprops for revision %ld"),
1159                              rev);
1160
1161   return SVN_NO_ERROR;
1162 }
1163
1164 /* Serialize the revision property list PROPLIST of revision REV in
1165  * filesystem FS to a non-packed file.  Return the name of that temporary
1166  * file in *TMP_PATH and the file path that it must be moved to in
1167  * *FINAL_PATH.
1168  *
1169  * Allocate *FINAL_PATH and *TMP_PATH in RESULT_POOL.  Use SCRATCH_POOL
1170  * for temporary allocations.
1171  */
1172 static svn_error_t *
1173 write_non_packed_revprop(const char **final_path,
1174                          const char **tmp_path,
1175                          svn_fs_t *fs,
1176                          svn_revnum_t rev,
1177                          apr_hash_t *proplist,
1178                          apr_pool_t *result_pool,
1179                          apr_pool_t *scratch_pool)
1180 {
1181   svn_stream_t *stream;
1182   *final_path = svn_fs_x__path_revprops(fs, rev, result_pool);
1183
1184   /* ### do we have a directory sitting around already? we really shouldn't
1185      ### have to get the dirname here. */
1186   SVN_ERR(svn_stream_open_unique(&stream, tmp_path,
1187                                  svn_dirent_dirname(*final_path,
1188                                                     scratch_pool),
1189                                  svn_io_file_del_none,
1190                                  result_pool, scratch_pool));
1191   SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR,
1192                           scratch_pool));
1193   SVN_ERR(svn_stream_close(stream));
1194
1195   return SVN_NO_ERROR;
1196 }
1197
1198 /* After writing the new revprop file(s), call this function to move the
1199  * file at TMP_PATH to FINAL_PATH and give it the permissions from
1200  * PERMS_REFERENCE.
1201  *
1202  * If indicated in BUMP_GENERATION, increase FS' revprop generation.
1203  * Finally, delete all the temporary files given in FILES_TO_DELETE.
1204  * The latter may be NULL.
1205  *
1206  * Use SCRATCH_POOL for temporary allocations.
1207  */
1208 static svn_error_t *
1209 switch_to_new_revprop(svn_fs_t *fs,
1210                       const char *final_path,
1211                       const char *tmp_path,
1212                       const char *perms_reference,
1213                       apr_array_header_t *files_to_delete,
1214                       svn_boolean_t bump_generation,
1215                       apr_pool_t *scratch_pool)
1216 {
1217   apr_int64_t generation;
1218
1219   /* Now, we may actually be replacing revprops. Make sure that all other
1220      threads and processes will know about this. */
1221   if (bump_generation)
1222     SVN_ERR(begin_revprop_change(&generation, fs, scratch_pool));
1223
1224   SVN_ERR(svn_fs_x__move_into_place(tmp_path, final_path, perms_reference,
1225                                     scratch_pool));
1226
1227   /* Indicate that the update (if relevant) has been completed. */
1228   if (bump_generation)
1229     SVN_ERR(end_revprop_change(fs, generation, scratch_pool));
1230
1231   /* Clean up temporary files, if necessary. */
1232   if (files_to_delete)
1233     {
1234       apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1235       int i;
1236
1237       for (i = 0; i < files_to_delete->nelts; ++i)
1238         {
1239           const char *path = APR_ARRAY_IDX(files_to_delete, i, const char*);
1240
1241           svn_pool_clear(iterpool);
1242           SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
1243         }
1244
1245       svn_pool_destroy(iterpool);
1246     }
1247   return SVN_NO_ERROR;
1248 }
1249
1250 /* Write a pack file header to STREAM that starts at revision START_REVISION
1251  * and contains the indexes [START,END) of SIZES.
1252  */
1253 static svn_error_t *
1254 serialize_revprops_header(svn_stream_t *stream,
1255                           svn_revnum_t start_revision,
1256                           apr_array_header_t *sizes,
1257                           int start,
1258                           int end,
1259                           apr_pool_t *scratch_pool)
1260 {
1261   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1262   int i;
1263
1264   SVN_ERR_ASSERT(start < end);
1265
1266   /* start revision and entry count */
1267   SVN_ERR(svn_stream_printf(stream, scratch_pool, "%ld\n", start_revision));
1268   SVN_ERR(svn_stream_printf(stream, scratch_pool, "%d\n", end - start));
1269
1270   /* the sizes array */
1271   for (i = start; i < end; ++i)
1272     {
1273       /* Non-standard pool usage.
1274        *
1275        * We only allocate a few bytes each iteration -- even with a
1276        * million iterations we would still be in good shape memory-wise.
1277        */
1278       apr_off_t size = APR_ARRAY_IDX(sizes, i, apr_off_t);
1279       SVN_ERR(svn_stream_printf(stream, iterpool, "%" APR_OFF_T_FMT "\n",
1280                                 size));
1281     }
1282
1283   /* the double newline char indicates the end of the header */
1284   SVN_ERR(svn_stream_printf(stream, iterpool, "\n"));
1285
1286   svn_pool_destroy(iterpool);
1287   return SVN_NO_ERROR;
1288 }
1289
1290 /* Writes the a pack file to FILE_STREAM.  It copies the serialized data
1291  * from REVPROPS for the indexes [START,END) except for index CHANGED_INDEX.
1292  *
1293  * The data for the latter is taken from NEW_SERIALIZED.  Note, that
1294  * CHANGED_INDEX may be outside the [START,END) range, i.e. no new data is
1295  * taken in that case but only a subset of the old data will be copied.
1296  *
1297  * NEW_TOTAL_SIZE is a hint for pre-allocating buffers of appropriate size.
1298  * SCRATCH_POOL is used for temporary allocations.
1299  */
1300 static svn_error_t *
1301 repack_revprops(svn_fs_t *fs,
1302                 packed_revprops_t *revprops,
1303                 int start,
1304                 int end,
1305                 int changed_index,
1306                 svn_stringbuf_t *new_serialized,
1307                 apr_off_t new_total_size,
1308                 svn_stream_t *file_stream,
1309                 apr_pool_t *scratch_pool)
1310 {
1311   svn_fs_x__data_t *ffd = fs->fsap_data;
1312   svn_stream_t *stream;
1313   int i;
1314
1315   /* create data empty buffers and the stream object */
1316   svn_stringbuf_t *uncompressed
1317     = svn_stringbuf_create_ensure((apr_size_t)new_total_size, scratch_pool);
1318   svn_stringbuf_t *compressed
1319     = svn_stringbuf_create_empty(scratch_pool);
1320   stream = svn_stream_from_stringbuf(uncompressed, scratch_pool);
1321
1322   /* write the header*/
1323   SVN_ERR(serialize_revprops_header(stream, revprops->start_revision + start,
1324                                     revprops->sizes, start, end,
1325                                     scratch_pool));
1326
1327   /* append the serialized revprops */
1328   for (i = start; i < end; ++i)
1329     if (i == changed_index)
1330       {
1331         SVN_ERR(svn_stream_write(stream,
1332                                  new_serialized->data,
1333                                  &new_serialized->len));
1334       }
1335     else
1336       {
1337         apr_size_t size
1338             = (apr_size_t)APR_ARRAY_IDX(revprops->sizes, i, apr_off_t);
1339         apr_size_t offset
1340             = (apr_size_t)APR_ARRAY_IDX(revprops->offsets, i, apr_off_t);
1341
1342         SVN_ERR(svn_stream_write(stream,
1343                                  revprops->packed_revprops->data + offset,
1344                                  &size));
1345       }
1346
1347   /* flush the stream buffer (if any) to our underlying data buffer */
1348   SVN_ERR(svn_stream_close(stream));
1349
1350   /* compress / store the data */
1351   SVN_ERR(svn__compress(uncompressed,
1352                         compressed,
1353                         ffd->compress_packed_revprops
1354                           ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
1355                           : SVN_DELTA_COMPRESSION_LEVEL_NONE));
1356
1357   /* finally, write the content to the target stream and close it */
1358   SVN_ERR(svn_stream_write(file_stream, compressed->data, &compressed->len));
1359   SVN_ERR(svn_stream_close(file_stream));
1360
1361   return SVN_NO_ERROR;
1362 }
1363
1364 /* Allocate a new pack file name for revisions
1365  *     [REVPROPS->START_REVISION + START, REVPROPS->START_REVISION + END - 1]
1366  * of REVPROPS->MANIFEST.  Add the name of old file to FILES_TO_DELETE,
1367  * auto-create that array if necessary.  Return an open file stream to
1368  * the new file in *STREAM allocated in RESULT_POOL.  Allocate the paths
1369  * in *FILES_TO_DELETE from the same pool that contains the array itself.
1370  *
1371  * Use SCRATCH_POOL for temporary allocations.
1372  */
1373 static svn_error_t *
1374 repack_stream_open(svn_stream_t **stream,
1375                    svn_fs_t *fs,
1376                    packed_revprops_t *revprops,
1377                    int start,
1378                    int end,
1379                    apr_array_header_t **files_to_delete,
1380                    apr_pool_t *result_pool,
1381                    apr_pool_t *scratch_pool)
1382 {
1383   apr_int64_t tag;
1384   const char *tag_string;
1385   svn_string_t *new_filename;
1386   int i;
1387   apr_file_t *file;
1388   int manifest_offset
1389     = (int)(revprops->start_revision - revprops->manifest_start);
1390
1391   /* get the old (= current) file name and enlist it for later deletion */
1392   const char *old_filename = APR_ARRAY_IDX(revprops->manifest,
1393                                            start + manifest_offset,
1394                                            const char*);
1395
1396   if (*files_to_delete == NULL)
1397     *files_to_delete = apr_array_make(result_pool, 3, sizeof(const char*));
1398
1399   APR_ARRAY_PUSH(*files_to_delete, const char*)
1400     = svn_dirent_join(revprops->folder, old_filename,
1401                       (*files_to_delete)->pool);
1402
1403   /* increase the tag part, i.e. the counter after the dot */
1404   tag_string = strchr(old_filename, '.');
1405   if (tag_string == NULL)
1406     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1407                              _("Packed file '%s' misses a tag"),
1408                              old_filename);
1409
1410   SVN_ERR(svn_cstring_atoi64(&tag, tag_string + 1));
1411   new_filename = svn_string_createf((*files_to_delete)->pool,
1412                                     "%ld.%" APR_INT64_T_FMT,
1413                                     revprops->start_revision + start,
1414                                     ++tag);
1415
1416   /* update the manifest to point to the new file */
1417   for (i = start; i < end; ++i)
1418     APR_ARRAY_IDX(revprops->manifest, i + manifest_offset, const char*)
1419       = new_filename->data;
1420
1421   /* create a file stream for the new file */
1422   SVN_ERR(svn_io_file_open(&file, svn_dirent_join(revprops->folder,
1423                                                   new_filename->data,
1424                                                   scratch_pool),
1425                            APR_WRITE | APR_CREATE, APR_OS_DEFAULT,
1426                            result_pool));
1427   *stream = svn_stream_from_aprfile2(file, FALSE, result_pool);
1428
1429   return SVN_NO_ERROR;
1430 }
1431
1432 /* For revision REV in filesystem FS, set the revision properties to
1433  * PROPLIST.  Return a new file in *TMP_PATH that the caller shall move
1434  * to *FINAL_PATH to make the change visible.  Files to be deleted will
1435  * be listed in *FILES_TO_DELETE which may remain unchanged / unallocated.
1436  *
1437  * Allocate output values in RESULT_POOL and temporaries from SCRATCH_POOL.
1438  */
1439 static svn_error_t *
1440 write_packed_revprop(const char **final_path,
1441                      const char **tmp_path,
1442                      apr_array_header_t **files_to_delete,
1443                      svn_fs_t *fs,
1444                      svn_revnum_t rev,
1445                      apr_hash_t *proplist,
1446                      apr_pool_t *result_pool,
1447                      apr_pool_t *scratch_pool)
1448 {
1449   svn_fs_x__data_t *ffd = fs->fsap_data;
1450   packed_revprops_t *revprops;
1451   apr_int64_t generation = 0;
1452   svn_stream_t *stream;
1453   svn_stringbuf_t *serialized;
1454   apr_off_t new_total_size;
1455   int changed_index;
1456
1457   /* read the current revprop generation. This value will not change
1458    * while we hold the global write lock to this FS. */
1459   if (has_revprop_cache(fs, scratch_pool))
1460     SVN_ERR(read_revprop_generation(&generation, fs, scratch_pool));
1461
1462   /* read contents of the current pack file */
1463   SVN_ERR(read_pack_revprop(&revprops, fs, rev, generation, TRUE,
1464                             scratch_pool, scratch_pool));
1465
1466   /* serialize the new revprops */
1467   serialized = svn_stringbuf_create_empty(scratch_pool);
1468   stream = svn_stream_from_stringbuf(serialized, scratch_pool);
1469   SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR,
1470                           scratch_pool));
1471   SVN_ERR(svn_stream_close(stream));
1472
1473   /* calculate the size of the new data */
1474   changed_index = (int)(rev - revprops->start_revision);
1475   new_total_size = revprops->total_size - revprops->serialized_size
1476                  + serialized->len
1477                  + (revprops->offsets->nelts + 2) * SVN_INT64_BUFFER_SIZE;
1478
1479   APR_ARRAY_IDX(revprops->sizes, changed_index, apr_off_t) = serialized->len;
1480
1481   /* can we put the new data into the same pack as the before? */
1482   if (   new_total_size < ffd->revprop_pack_size
1483       || revprops->sizes->nelts == 1)
1484     {
1485       /* simply replace the old pack file with new content as we do it
1486        * in the non-packed case */
1487
1488       *final_path = svn_dirent_join(revprops->folder, revprops->filename,
1489                                     result_pool);
1490       SVN_ERR(svn_stream_open_unique(&stream, tmp_path, revprops->folder,
1491                                      svn_io_file_del_none, result_pool,
1492                                      scratch_pool));
1493       SVN_ERR(repack_revprops(fs, revprops, 0, revprops->sizes->nelts,
1494                               changed_index, serialized, new_total_size,
1495                               stream, scratch_pool));
1496     }
1497   else
1498     {
1499       /* split the pack file into two of roughly equal size */
1500       int right_count, left_count, i;
1501
1502       int left = 0;
1503       int right = revprops->sizes->nelts - 1;
1504       apr_off_t left_size = 2 * SVN_INT64_BUFFER_SIZE;
1505       apr_off_t right_size = 2 * SVN_INT64_BUFFER_SIZE;
1506
1507       /* let left and right side grow such that their size difference
1508        * is minimal after each step. */
1509       while (left <= right)
1510         if (  left_size + APR_ARRAY_IDX(revprops->sizes, left, apr_off_t)
1511             < right_size + APR_ARRAY_IDX(revprops->sizes, right, apr_off_t))
1512           {
1513             left_size += APR_ARRAY_IDX(revprops->sizes, left, apr_off_t)
1514                       + SVN_INT64_BUFFER_SIZE;
1515             ++left;
1516           }
1517         else
1518           {
1519             right_size += APR_ARRAY_IDX(revprops->sizes, right, apr_off_t)
1520                         + SVN_INT64_BUFFER_SIZE;
1521             --right;
1522           }
1523
1524        /* since the items need much less than SVN_INT64_BUFFER_SIZE
1525         * bytes to represent their length, the split may not be optimal */
1526       left_count = left;
1527       right_count = revprops->sizes->nelts - left;
1528
1529       /* if new_size is large, one side may exceed the pack size limit.
1530        * In that case, split before and after the modified revprop.*/
1531       if (   left_size > ffd->revprop_pack_size
1532           || right_size > ffd->revprop_pack_size)
1533         {
1534           left_count = changed_index;
1535           right_count = revprops->sizes->nelts - left_count - 1;
1536         }
1537
1538       /* Allocate this here such that we can call the repack functions with
1539        * the scratch pool alone. */
1540       if (*files_to_delete == NULL)
1541         *files_to_delete = apr_array_make(result_pool, 3,
1542                                           sizeof(const char*));
1543
1544       /* write the new, split files */
1545       if (left_count)
1546         {
1547           SVN_ERR(repack_stream_open(&stream, fs, revprops, 0,
1548                                      left_count, files_to_delete,
1549                                      scratch_pool, scratch_pool));
1550           SVN_ERR(repack_revprops(fs, revprops, 0, left_count,
1551                                   changed_index, serialized, new_total_size,
1552                                   stream, scratch_pool));
1553         }
1554
1555       if (left_count + right_count < revprops->sizes->nelts)
1556         {
1557           SVN_ERR(repack_stream_open(&stream, fs, revprops, changed_index,
1558                                      changed_index + 1, files_to_delete,
1559                                      scratch_pool, scratch_pool));
1560           SVN_ERR(repack_revprops(fs, revprops, changed_index,
1561                                   changed_index + 1,
1562                                   changed_index, serialized, new_total_size,
1563                                   stream, scratch_pool));
1564         }
1565
1566       if (right_count)
1567         {
1568           SVN_ERR(repack_stream_open(&stream, fs, revprops,
1569                                      revprops->sizes->nelts - right_count,
1570                                      revprops->sizes->nelts,
1571                                      files_to_delete, scratch_pool,
1572                                      scratch_pool));
1573           SVN_ERR(repack_revprops(fs, revprops,
1574                                   revprops->sizes->nelts - right_count,
1575                                   revprops->sizes->nelts, changed_index,
1576                                   serialized, new_total_size, stream,
1577                                   scratch_pool));
1578         }
1579
1580       /* write the new manifest */
1581       *final_path = svn_dirent_join(revprops->folder, PATH_MANIFEST,
1582                                     result_pool);
1583       SVN_ERR(svn_stream_open_unique(&stream, tmp_path, revprops->folder,
1584                                      svn_io_file_del_none, result_pool,
1585                                      scratch_pool));
1586
1587       for (i = 0; i < revprops->manifest->nelts; ++i)
1588         {
1589           const char *filename = APR_ARRAY_IDX(revprops->manifest, i,
1590                                                const char*);
1591           SVN_ERR(svn_stream_printf(stream, scratch_pool, "%s\n", filename));
1592         }
1593
1594       SVN_ERR(svn_stream_close(stream));
1595     }
1596
1597   return SVN_NO_ERROR;
1598 }
1599
1600 /* Set the revision property list of revision REV in filesystem FS to
1601    PROPLIST.  Use SCRATCH_POOL for temporary allocations. */
1602 svn_error_t *
1603 svn_fs_x__set_revision_proplist(svn_fs_t *fs,
1604                                 svn_revnum_t rev,
1605                                 apr_hash_t *proplist,
1606                                 apr_pool_t *scratch_pool)
1607 {
1608   svn_boolean_t is_packed;
1609   svn_boolean_t bump_generation = FALSE;
1610   const char *final_path;
1611   const char *tmp_path;
1612   const char *perms_reference;
1613   apr_array_header_t *files_to_delete = NULL;
1614
1615   SVN_ERR(svn_fs_x__ensure_revision_exists(rev, fs, scratch_pool));
1616
1617   /* this info will not change while we hold the global FS write lock */
1618   is_packed = svn_fs_x__is_packed_revprop(fs, rev);
1619
1620   /* Test whether revprops already exist for this revision.
1621    * Only then will we need to bump the revprop generation.
1622    * The fact that they did not yet exist is never cached. */
1623   if (is_packed)
1624     {
1625       bump_generation = TRUE;
1626     }
1627   else
1628     {
1629       svn_node_kind_t kind;
1630       SVN_ERR(svn_io_check_path(svn_fs_x__path_revprops(fs, rev,
1631                                                         scratch_pool),
1632                                 &kind, scratch_pool));
1633       bump_generation = kind != svn_node_none;
1634     }
1635
1636   /* Serialize the new revprop data */
1637   if (is_packed)
1638     SVN_ERR(write_packed_revprop(&final_path, &tmp_path, &files_to_delete,
1639                                  fs, rev, proplist, scratch_pool,
1640                                  scratch_pool));
1641   else
1642     SVN_ERR(write_non_packed_revprop(&final_path, &tmp_path,
1643                                      fs, rev, proplist, scratch_pool,
1644                                      scratch_pool));
1645
1646   /* We use the rev file of this revision as the perms reference,
1647    * because when setting revprops for the first time, the revprop
1648    * file won't exist and therefore can't serve as its own reference.
1649    * (Whereas the rev file should already exist at this point.)
1650    */
1651   perms_reference = svn_fs_x__path_rev_absolute(fs, rev, scratch_pool);
1652
1653   /* Now, switch to the new revprop data. */
1654   SVN_ERR(switch_to_new_revprop(fs, final_path, tmp_path, perms_reference,
1655                                 files_to_delete, bump_generation,
1656                                 scratch_pool));
1657
1658   return SVN_NO_ERROR;
1659 }
1660
1661 /* Return TRUE, if for REVISION in FS, we can find the revprop pack file.
1662  * Use SCRATCH_POOL for temporary allocations.
1663  * Set *MISSING, if the reason is a missing manifest or pack file.
1664  */
1665 svn_boolean_t
1666 svn_fs_x__packed_revprop_available(svn_boolean_t *missing,
1667                                    svn_fs_t *fs,
1668                                    svn_revnum_t revision,
1669                                    apr_pool_t *scratch_pool)
1670 {
1671   svn_fs_x__data_t *ffd = fs->fsap_data;
1672   svn_stringbuf_t *content = NULL;
1673
1674   /* try to read the manifest file */
1675   const char *folder = svn_fs_x__path_revprops_pack_shard(fs, revision,
1676                                                           scratch_pool);
1677   const char *manifest_path = svn_dirent_join(folder, PATH_MANIFEST,
1678                                               scratch_pool);
1679
1680   svn_error_t *err = svn_fs_x__try_stringbuf_from_file(&content,
1681                                                        missing,
1682                                                        manifest_path,
1683                                                        FALSE,
1684                                                        scratch_pool);
1685
1686   /* if the manifest cannot be read, consider the pack files inaccessible
1687    * even if the file itself exists. */
1688   if (err)
1689     {
1690       svn_error_clear(err);
1691       return FALSE;
1692     }
1693
1694   if (*missing)
1695     return FALSE;
1696
1697   /* parse manifest content until we find the entry for REVISION.
1698    * Revision 0 is never packed. */
1699   revision = revision < ffd->max_files_per_dir
1700            ? revision - 1
1701            : revision % ffd->max_files_per_dir;
1702   while (content->data)
1703     {
1704       char *next = strchr(content->data, '\n');
1705       if (next)
1706         {
1707           *next = 0;
1708           ++next;
1709         }
1710
1711       if (revision-- == 0)
1712         {
1713           /* the respective pack file must exist (and be a file) */
1714           svn_node_kind_t kind;
1715           err = svn_io_check_path(svn_dirent_join(folder, content->data,
1716                                                   scratch_pool),
1717                                   &kind, scratch_pool);
1718           if (err)
1719             {
1720               svn_error_clear(err);
1721               return FALSE;
1722             }
1723
1724           *missing = kind == svn_node_none;
1725           return kind == svn_node_file;
1726         }
1727
1728       content->data = next;
1729     }
1730
1731   return FALSE;
1732 }
1733
1734 \f
1735 /****** Packing FSX shards *********/
1736
1737 svn_error_t *
1738 svn_fs_x__copy_revprops(const char *pack_file_dir,
1739                         const char *pack_filename,
1740                         const char *shard_path,
1741                         svn_revnum_t start_rev,
1742                         svn_revnum_t end_rev,
1743                         apr_array_header_t *sizes,
1744                         apr_size_t total_size,
1745                         int compression_level,
1746                         svn_cancel_func_t cancel_func,
1747                         void *cancel_baton,
1748                         apr_pool_t *scratch_pool)
1749 {
1750   svn_stream_t *pack_stream;
1751   apr_file_t *pack_file;
1752   svn_revnum_t rev;
1753   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1754   svn_stream_t *stream;
1755
1756   /* create empty data buffer and a write stream on top of it */
1757   svn_stringbuf_t *uncompressed
1758     = svn_stringbuf_create_ensure(total_size, scratch_pool);
1759   svn_stringbuf_t *compressed
1760     = svn_stringbuf_create_empty(scratch_pool);
1761   pack_stream = svn_stream_from_stringbuf(uncompressed, scratch_pool);
1762
1763   /* write the pack file header */
1764   SVN_ERR(serialize_revprops_header(pack_stream, start_rev, sizes, 0,
1765                                     sizes->nelts, iterpool));
1766
1767   /* Some useful paths. */
1768   SVN_ERR(svn_io_file_open(&pack_file, svn_dirent_join(pack_file_dir,
1769                                                        pack_filename,
1770                                                        scratch_pool),
1771                            APR_WRITE | APR_CREATE, APR_OS_DEFAULT,
1772                            scratch_pool));
1773
1774   /* Iterate over the revisions in this shard, squashing them together. */
1775   for (rev = start_rev; rev <= end_rev; rev++)
1776     {
1777       const char *path;
1778
1779       svn_pool_clear(iterpool);
1780
1781       /* Construct the file name. */
1782       path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
1783                              iterpool);
1784
1785       /* Copy all the bits from the non-packed revprop file to the end of
1786        * the pack file. */
1787       SVN_ERR(svn_stream_open_readonly(&stream, path, iterpool, iterpool));
1788       SVN_ERR(svn_stream_copy3(stream, pack_stream,
1789                                cancel_func, cancel_baton, iterpool));
1790     }
1791
1792   /* flush stream buffers to content buffer */
1793   SVN_ERR(svn_stream_close(pack_stream));
1794
1795   /* compress the content (or just store it for COMPRESSION_LEVEL 0) */
1796   SVN_ERR(svn__compress(uncompressed, compressed, compression_level));
1797
1798   /* write the pack file content to disk */
1799   stream = svn_stream_from_aprfile2(pack_file, FALSE, scratch_pool);
1800   SVN_ERR(svn_stream_write(stream, compressed->data, &compressed->len));
1801   SVN_ERR(svn_stream_close(stream));
1802
1803   svn_pool_destroy(iterpool);
1804
1805   return SVN_NO_ERROR;
1806 }
1807
1808 svn_error_t *
1809 svn_fs_x__pack_revprops_shard(const char *pack_file_dir,
1810                               const char *shard_path,
1811                               apr_int64_t shard,
1812                               int max_files_per_dir,
1813                               apr_off_t max_pack_size,
1814                               int compression_level,
1815                               svn_cancel_func_t cancel_func,
1816                               void *cancel_baton,
1817                               apr_pool_t *scratch_pool)
1818 {
1819   const char *manifest_file_path, *pack_filename = NULL;
1820   svn_stream_t *manifest_stream;
1821   svn_revnum_t start_rev, end_rev, rev;
1822   apr_off_t total_size;
1823   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1824   apr_array_header_t *sizes;
1825
1826   /* Some useful paths. */
1827   manifest_file_path = svn_dirent_join(pack_file_dir, PATH_MANIFEST,
1828                                        scratch_pool);
1829
1830   /* Remove any existing pack file for this shard, since it is incomplete. */
1831   SVN_ERR(svn_io_remove_dir2(pack_file_dir, TRUE, cancel_func, cancel_baton,
1832                              scratch_pool));
1833
1834   /* Create the new directory and manifest file stream. */
1835   SVN_ERR(svn_io_dir_make(pack_file_dir, APR_OS_DEFAULT, scratch_pool));
1836   SVN_ERR(svn_stream_open_writable(&manifest_stream, manifest_file_path,
1837                                    scratch_pool, scratch_pool));
1838
1839   /* revisions to handle. Special case: revision 0 */
1840   start_rev = (svn_revnum_t) (shard * max_files_per_dir);
1841   end_rev = (svn_revnum_t) ((shard + 1) * (max_files_per_dir) - 1);
1842   if (start_rev == 0)
1843     ++start_rev;
1844     /* Special special case: if max_files_per_dir is 1, then at this point
1845        start_rev == 1 and end_rev == 0 (!).  Fortunately, everything just
1846        works. */
1847
1848   /* initialize the revprop size info */
1849   sizes = apr_array_make(scratch_pool, max_files_per_dir, sizeof(apr_off_t));
1850   total_size = 2 * SVN_INT64_BUFFER_SIZE;
1851
1852   /* Iterate over the revisions in this shard, determine their size and
1853    * squashing them together into pack files. */
1854   for (rev = start_rev; rev <= end_rev; rev++)
1855     {
1856       apr_finfo_t finfo;
1857       const char *path;
1858
1859       svn_pool_clear(iterpool);
1860
1861       /* Get the size of the file. */
1862       path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
1863                              iterpool);
1864       SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_SIZE, iterpool));
1865
1866       /* if we already have started a pack file and this revprop cannot be
1867        * appended to it, write the previous pack file. */
1868       if (sizes->nelts != 0 &&
1869           total_size + SVN_INT64_BUFFER_SIZE + finfo.size > max_pack_size)
1870         {
1871           SVN_ERR(svn_fs_x__copy_revprops(pack_file_dir, pack_filename,
1872                                           shard_path, start_rev, rev-1,
1873                                           sizes, (apr_size_t)total_size,
1874                                           compression_level, cancel_func,
1875                                           cancel_baton, iterpool));
1876
1877           /* next pack file starts empty again */
1878           apr_array_clear(sizes);
1879           total_size = 2 * SVN_INT64_BUFFER_SIZE;
1880           start_rev = rev;
1881         }
1882
1883       /* Update the manifest. Allocate a file name for the current pack
1884        * file if it is a new one */
1885       if (sizes->nelts == 0)
1886         pack_filename = apr_psprintf(scratch_pool, "%ld.0", rev);
1887
1888       SVN_ERR(svn_stream_printf(manifest_stream, iterpool, "%s\n",
1889                                 pack_filename));
1890
1891       /* add to list of files to put into the current pack file */
1892       APR_ARRAY_PUSH(sizes, apr_off_t) = finfo.size;
1893       total_size += SVN_INT64_BUFFER_SIZE + finfo.size;
1894     }
1895
1896   /* write the last pack file */
1897   if (sizes->nelts != 0)
1898     SVN_ERR(svn_fs_x__copy_revprops(pack_file_dir, pack_filename, shard_path,
1899                                     start_rev, rev-1, sizes,
1900                                     (apr_size_t)total_size, compression_level,
1901                                     cancel_func, cancel_baton, iterpool));
1902
1903   /* flush the manifest file and update permissions */
1904   SVN_ERR(svn_stream_close(manifest_stream));
1905   SVN_ERR(svn_io_copy_perms(shard_path, pack_file_dir, iterpool));
1906
1907   svn_pool_destroy(iterpool);
1908
1909   return SVN_NO_ERROR;
1910 }
1911
1912 svn_error_t *
1913 svn_fs_x__delete_revprops_shard(const char *shard_path,
1914                                 apr_int64_t shard,
1915                                 int max_files_per_dir,
1916                                 svn_cancel_func_t cancel_func,
1917                                 void *cancel_baton,
1918                                 apr_pool_t *scratch_pool)
1919 {
1920   if (shard == 0)
1921     {
1922       apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1923       int i;
1924
1925       /* delete all files except the one for revision 0 */
1926       for (i = 1; i < max_files_per_dir; ++i)
1927         {
1928           const char *path;
1929           svn_pool_clear(iterpool);
1930
1931           path = svn_dirent_join(shard_path,
1932                                  apr_psprintf(iterpool, "%d", i),
1933                                  iterpool);
1934           if (cancel_func)
1935             SVN_ERR((*cancel_func)(cancel_baton));
1936
1937           SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
1938         }
1939
1940       svn_pool_destroy(iterpool);
1941     }
1942   else
1943     SVN_ERR(svn_io_remove_dir2(shard_path, TRUE,
1944                                cancel_func, cancel_baton, scratch_pool));
1945
1946   return SVN_NO_ERROR;
1947 }
1948