]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_fs_x/util.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_fs_x / util.c
1 /* util.c --- utility functions for FSX repo access
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_ctype.h"
26 #include "svn_dirent_uri.h"
27 #include "private/svn_string_private.h"
28
29 #include "fs_x.h"
30 #include "id.h"
31 #include "util.h"
32
33 #include "../libsvn_fs/fs-loader.h"
34
35 #include "svn_private_config.h"
36
37 /* Following are defines that specify the textual elements of the
38    native filesystem directories and revision files. */
39
40 /* Notes:
41
42 To avoid opening and closing the rev-files all the time, it would
43 probably be advantageous to keep each rev-file open for the
44 lifetime of the transaction object.  I'll leave that as a later
45 optimization for now.
46
47 I didn't keep track of pool lifetimes at all in this code.  There
48 are likely some errors because of that.
49
50 */
51
52 /* Pathname helper functions */
53
54 /* Return TRUE is REV is packed in FS, FALSE otherwise. */
55 svn_boolean_t
56 svn_fs_x__is_packed_rev(svn_fs_t *fs, svn_revnum_t rev)
57 {
58   svn_fs_x__data_t *ffd = fs->fsap_data;
59
60   return (rev < ffd->min_unpacked_rev);
61 }
62
63 /* Return TRUE is REV is packed in FS, FALSE otherwise. */
64 svn_boolean_t
65 svn_fs_x__is_packed_revprop(svn_fs_t *fs, svn_revnum_t rev)
66 {
67   svn_fs_x__data_t *ffd = fs->fsap_data;
68
69   /* rev 0 will not be packed */
70   return (rev < ffd->min_unpacked_rev) && (rev != 0);
71 }
72
73 svn_revnum_t
74 svn_fs_x__packed_base_rev(svn_fs_t *fs, svn_revnum_t rev)
75 {
76   svn_fs_x__data_t *ffd = fs->fsap_data;
77
78   return rev < ffd->min_unpacked_rev
79        ? rev - (rev % ffd->max_files_per_dir)
80        : rev;
81 }
82
83 svn_revnum_t
84 svn_fs_x__pack_size(svn_fs_t *fs, svn_revnum_t rev)
85 {
86   svn_fs_x__data_t *ffd = fs->fsap_data;
87
88   return rev < ffd->min_unpacked_rev ? ffd->max_files_per_dir : 1;
89 }
90
91 const char *
92 svn_fs_x__path_format(svn_fs_t *fs,
93                       apr_pool_t *result_pool)
94 {
95   return svn_dirent_join(fs->path, PATH_FORMAT, result_pool);
96 }
97
98 const char *
99 svn_fs_x__path_uuid(svn_fs_t *fs,
100                     apr_pool_t *result_pool)
101 {
102   return svn_dirent_join(fs->path, PATH_UUID, result_pool);
103 }
104
105 const char *
106 svn_fs_x__path_current(svn_fs_t *fs,
107                        apr_pool_t *result_pool)
108 {
109   return svn_dirent_join(fs->path, PATH_CURRENT, result_pool);
110 }
111
112 const char *
113 svn_fs_x__path_txn_current(svn_fs_t *fs,
114                            apr_pool_t *result_pool)
115 {
116   return svn_dirent_join(fs->path, PATH_TXN_CURRENT,
117                          result_pool);
118 }
119
120 const char *
121 svn_fs_x__path_txn_current_lock(svn_fs_t *fs,
122                                 apr_pool_t *result_pool)
123 {
124   return svn_dirent_join(fs->path, PATH_TXN_CURRENT_LOCK, result_pool);
125 }
126
127 const char *
128 svn_fs_x__path_lock(svn_fs_t *fs,
129                     apr_pool_t *result_pool)
130 {
131   return svn_dirent_join(fs->path, PATH_LOCK_FILE, result_pool);
132 }
133
134 const char *
135 svn_fs_x__path_pack_lock(svn_fs_t *fs,
136                          apr_pool_t *result_pool)
137 {
138   return svn_dirent_join(fs->path, PATH_PACK_LOCK_FILE, result_pool);
139 }
140
141 const char *
142 svn_fs_x__path_revprop_generation(svn_fs_t *fs,
143                                   apr_pool_t *result_pool)
144 {
145   return svn_dirent_join(fs->path, PATH_REVPROP_GENERATION, result_pool);
146 }
147
148 /* Return the full path of the file FILENAME within revision REV's shard in
149  * FS.  If FILENAME is NULL, return the shard directory directory itself.
150  * REVPROPS indicates the parent of the shard parent folder ("revprops" or
151  * "revs").  PACKED says whether we want the packed shard's name.
152  *
153  * Allocate the result in RESULT_POOL.
154  */static const char*
155 construct_shard_sub_path(svn_fs_t *fs,
156                          svn_revnum_t rev,
157                          svn_boolean_t revprops,
158                          svn_boolean_t packed,
159                          const char *filename,
160                          apr_pool_t *result_pool)
161 {
162   svn_fs_x__data_t *ffd = fs->fsap_data;
163   char buffer[SVN_INT64_BUFFER_SIZE + sizeof(PATH_EXT_PACKED_SHARD)] = { 0 };
164
165   /* Select the appropriate parent path constant. */
166   const char *parent = revprops ? PATH_REVPROPS_DIR : PATH_REVS_DIR;
167
168   /* String containing the shard number. */
169   apr_size_t len = svn__i64toa(buffer, rev / ffd->max_files_per_dir);
170
171   /* Append the suffix.  Limit it to the buffer size (should never hit it). */
172   if (packed)
173     strncpy(buffer + len, PATH_EXT_PACKED_SHARD, sizeof(buffer) - len - 1);
174
175   /* This will also work for NULL FILENAME as well. */
176   return svn_dirent_join_many(result_pool, fs->path, parent, buffer,
177                               filename, SVN_VA_NULL);
178 }
179
180 const char *
181 svn_fs_x__path_rev_packed(svn_fs_t *fs,
182                           svn_revnum_t rev,
183                           const char *kind,
184                           apr_pool_t *result_pool)
185 {
186   assert(svn_fs_x__is_packed_rev(fs, rev));
187   return construct_shard_sub_path(fs, rev, FALSE, TRUE, kind, result_pool);
188 }
189
190 const char *
191 svn_fs_x__path_rev_shard(svn_fs_t *fs,
192                          svn_revnum_t rev,
193                          apr_pool_t *result_pool)
194 {
195   return construct_shard_sub_path(fs, rev, FALSE, FALSE, NULL, result_pool);
196 }
197
198 const char *
199 svn_fs_x__path_rev(svn_fs_t *fs,
200                    svn_revnum_t rev,
201                    apr_pool_t *result_pool)
202 {
203   char buffer[SVN_INT64_BUFFER_SIZE];
204   svn__i64toa(buffer, rev);
205
206   assert(! svn_fs_x__is_packed_rev(fs, rev));
207   return construct_shard_sub_path(fs, rev, FALSE, FALSE, buffer, result_pool);
208 }
209
210 const char *
211 svn_fs_x__path_rev_absolute(svn_fs_t *fs,
212                             svn_revnum_t rev,
213                             apr_pool_t *result_pool)
214 {
215   return svn_fs_x__is_packed_rev(fs, rev)
216        ? svn_fs_x__path_rev_packed(fs, rev, PATH_PACKED, result_pool)
217        : svn_fs_x__path_rev(fs, rev, result_pool);
218 }
219
220 const char *
221 svn_fs_x__path_revprops_shard(svn_fs_t *fs,
222                               svn_revnum_t rev,
223                               apr_pool_t *result_pool)
224 {
225   return construct_shard_sub_path(fs, rev, TRUE, FALSE, NULL, result_pool);
226 }
227
228 const char *
229 svn_fs_x__path_revprops_pack_shard(svn_fs_t *fs,
230                                    svn_revnum_t rev,
231                                    apr_pool_t *result_pool)
232 {
233   return construct_shard_sub_path(fs, rev, TRUE, TRUE, NULL, result_pool);
234 }
235
236 const char *
237 svn_fs_x__path_revprops(svn_fs_t *fs,
238                         svn_revnum_t rev,
239                         apr_pool_t *result_pool)
240 {
241   char buffer[SVN_INT64_BUFFER_SIZE];
242   svn__i64toa(buffer, rev);
243
244   assert(! svn_fs_x__is_packed_revprop(fs, rev));
245   return construct_shard_sub_path(fs, rev, TRUE, FALSE, buffer, result_pool);
246 }
247
248 const char *
249 svn_fs_x__txn_name(svn_fs_x__txn_id_t txn_id,
250                    apr_pool_t *result_pool)
251 {
252   char *p = apr_palloc(result_pool, SVN_INT64_BUFFER_SIZE);
253   svn__ui64tobase36(p, txn_id);
254   return p;
255 }
256
257 svn_error_t *
258 svn_fs_x__txn_by_name(svn_fs_x__txn_id_t *txn_id,
259                       const char *txn_name)
260 {
261   const char *next;
262   apr_uint64_t id = svn__base36toui64(&next, txn_name);
263   if (next == NULL || *next != 0 || *txn_name == 0)
264     return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
265                              "Malformed TXN name '%s'", txn_name);
266
267   *txn_id = id;
268   return SVN_NO_ERROR;
269 }
270
271 const char *
272 svn_fs_x__path_txns_dir(svn_fs_t *fs,
273                         apr_pool_t *result_pool)
274 {
275   return svn_dirent_join(fs->path, PATH_TXNS_DIR, result_pool);
276 }
277
278 /* Return the full path of the file FILENAME within transaction TXN_ID's
279  * transaction directory in FS.  If FILENAME is NULL, return the transaction
280  * directory itself.
281  *
282  * Allocate the result in RESULT_POOL.
283  */
284 static const char *
285 construct_txn_path(svn_fs_t *fs,
286                    svn_fs_x__txn_id_t txn_id,
287                    const char *filename,
288                    apr_pool_t *result_pool)
289 {
290   /* Construct the transaction directory name without temp. allocations. */
291   char buffer[SVN_INT64_BUFFER_SIZE + sizeof(PATH_EXT_TXN)] = { 0 };
292   apr_size_t len = svn__ui64tobase36(buffer, txn_id);
293   strncpy(buffer + len, PATH_EXT_TXN, sizeof(buffer) - len - 1);
294
295   /* If FILENAME is NULL, it will terminate the list of segments
296      to concatenate. */
297   return svn_dirent_join_many(result_pool, fs->path, PATH_TXNS_DIR,
298                               buffer, filename, SVN_VA_NULL);
299 }
300
301 const char *
302 svn_fs_x__path_txn_dir(svn_fs_t *fs,
303                        svn_fs_x__txn_id_t txn_id,
304                        apr_pool_t *result_pool)
305 {
306   return construct_txn_path(fs, txn_id, NULL, result_pool);
307 }
308
309 /* Return the name of the sha1->rep mapping file in transaction TXN_ID
310  * within FS for the given SHA1 checksum.  Use POOL for allocations.
311  */
312 const char *
313 svn_fs_x__path_txn_sha1(svn_fs_t *fs,
314                         svn_fs_x__txn_id_t txn_id,
315                         const unsigned char *sha1,
316                         apr_pool_t *pool)
317 {
318   svn_checksum_t checksum;
319   checksum.digest = sha1;
320   checksum.kind = svn_checksum_sha1;
321
322   return svn_dirent_join(svn_fs_x__path_txn_dir(fs, txn_id, pool),
323                          svn_checksum_to_cstring(&checksum, pool),
324                          pool);
325 }
326
327 const char *
328 svn_fs_x__path_txn_changes(svn_fs_t *fs,
329                            svn_fs_x__txn_id_t txn_id,
330                            apr_pool_t *result_pool)
331 {
332   return construct_txn_path(fs, txn_id, PATH_CHANGES, result_pool);
333 }
334
335 const char *
336 svn_fs_x__path_txn_props(svn_fs_t *fs,
337                          svn_fs_x__txn_id_t txn_id,
338                          apr_pool_t *result_pool)
339 {
340   return construct_txn_path(fs, txn_id, PATH_TXN_PROPS, result_pool);
341 }
342
343 const char *
344 svn_fs_x__path_txn_props_final(svn_fs_t *fs,
345                                svn_fs_x__txn_id_t txn_id,
346                                apr_pool_t *result_pool)
347 {
348   return construct_txn_path(fs, txn_id, PATH_TXN_PROPS_FINAL, result_pool);
349 }
350
351 const char*
352 svn_fs_x__path_l2p_proto_index(svn_fs_t *fs,
353                                svn_fs_x__txn_id_t txn_id,
354                                apr_pool_t *result_pool)
355 {
356   return construct_txn_path(fs, txn_id, PATH_INDEX PATH_EXT_L2P_INDEX,
357                             result_pool);
358 }
359
360 const char*
361 svn_fs_x__path_p2l_proto_index(svn_fs_t *fs,
362                                svn_fs_x__txn_id_t txn_id,
363                                apr_pool_t *result_pool)
364 {
365   return construct_txn_path(fs, txn_id, PATH_INDEX PATH_EXT_P2L_INDEX,
366                             result_pool);
367 }
368
369 const char *
370 svn_fs_x__path_txn_next_ids(svn_fs_t *fs,
371                             svn_fs_x__txn_id_t txn_id,
372                             apr_pool_t *result_pool)
373 {
374   return construct_txn_path(fs, txn_id, PATH_NEXT_IDS, result_pool);
375 }
376
377 const char *
378 svn_fs_x__path_min_unpacked_rev(svn_fs_t *fs,
379                                 apr_pool_t *result_pool)
380 {
381   return svn_dirent_join(fs->path, PATH_MIN_UNPACKED_REV, result_pool);
382 }
383
384 const char *
385 svn_fs_x__path_txn_proto_revs(svn_fs_t *fs,
386                               apr_pool_t *result_pool)
387 {
388   return svn_dirent_join(fs->path, PATH_TXN_PROTOS_DIR, result_pool);
389 }
390
391 const char *
392 svn_fs_x__path_txn_item_index(svn_fs_t *fs,
393                               svn_fs_x__txn_id_t txn_id,
394                               apr_pool_t *result_pool)
395 {
396   return construct_txn_path(fs, txn_id, PATH_TXN_ITEM_INDEX, result_pool);
397 }
398
399 /* Return the full path of the proto-rev file / lock file for transaction
400  * TXN_ID in FS.  The SUFFIX determines what file (rev / lock) it will be.
401  *
402  * Allocate the result in RESULT_POOL.
403  */
404 static const char *
405 construct_proto_rev_path(svn_fs_t *fs,
406                          svn_fs_x__txn_id_t txn_id,
407                          const char *suffix,
408                          apr_pool_t *result_pool)
409 {
410   /* Construct the file name without temp. allocations. */
411   char buffer[SVN_INT64_BUFFER_SIZE + sizeof(PATH_EXT_REV_LOCK)] = { 0 };
412   apr_size_t len = svn__ui64tobase36(buffer, txn_id);
413   strncpy(buffer + len, suffix, sizeof(buffer) - len - 1);
414
415   /* If FILENAME is NULL, it will terminate the list of segments
416      to concatenate. */
417   return svn_dirent_join_many(result_pool, fs->path, PATH_TXN_PROTOS_DIR,
418                               buffer, SVN_VA_NULL);
419 }
420
421 const char *
422 svn_fs_x__path_txn_proto_rev(svn_fs_t *fs,
423                              svn_fs_x__txn_id_t txn_id,
424                              apr_pool_t *result_pool)
425 {
426   return construct_proto_rev_path(fs, txn_id, PATH_EXT_REV, result_pool);
427 }
428
429 const char *
430 svn_fs_x__path_txn_proto_rev_lock(svn_fs_t *fs,
431                                   svn_fs_x__txn_id_t txn_id,
432                                   apr_pool_t *result_pool)
433 {
434   return construct_proto_rev_path(fs, txn_id, PATH_EXT_REV_LOCK, result_pool);
435 }
436
437 /* Return the full path of the noderev-related file with the extension SUFFIX
438  * for noderev *ID in transaction TXN_ID in FS.
439  *
440  * Allocate the result in RESULT_POOL and temporaries in SCRATCH_POOL.
441  */
442 static const char *
443 construct_txn_node_path(svn_fs_t *fs,
444                         const svn_fs_x__id_t *id,
445                         const char *suffix,
446                         apr_pool_t *result_pool,
447                         apr_pool_t *scratch_pool)
448 {
449   const char *filename = svn_fs_x__id_unparse(id, result_pool)->data;
450   apr_int64_t txn_id = svn_fs_x__get_txn_id(id->change_set);
451
452   return svn_dirent_join(svn_fs_x__path_txn_dir(fs, txn_id, scratch_pool),
453                          apr_psprintf(scratch_pool, PATH_PREFIX_NODE "%s%s",
454                                       filename, suffix),
455                          result_pool);
456 }
457
458 const char *
459 svn_fs_x__path_txn_node_rev(svn_fs_t *fs,
460                             const svn_fs_x__id_t *id,
461                             apr_pool_t *result_pool,
462                             apr_pool_t *scratch_pool)
463 {
464   return construct_txn_node_path(fs, id, "", result_pool, scratch_pool);
465 }
466
467 const char *
468 svn_fs_x__path_txn_node_props(svn_fs_t *fs,
469                               const svn_fs_x__id_t *id,
470                               apr_pool_t *result_pool,
471                               apr_pool_t *scratch_pool)
472 {
473   return construct_txn_node_path(fs, id, PATH_EXT_PROPS, result_pool,
474                                  scratch_pool);
475 }
476
477 const char *
478 svn_fs_x__path_txn_node_children(svn_fs_t *fs,
479                                  const svn_fs_x__id_t *id,
480                                  apr_pool_t *result_pool,
481                                  apr_pool_t *scratch_pool)
482 {
483   return construct_txn_node_path(fs, id, PATH_EXT_CHILDREN, result_pool,
484                                  scratch_pool);
485 }
486
487 svn_error_t *
488 svn_fs_x__check_file_buffer_numeric(const char *buf,
489                                     apr_off_t offset,
490                                     const char *path,
491                                     const char *title,
492                                     apr_pool_t *scratch_pool)
493 {
494   const char *p;
495
496   for (p = buf + offset; *p; p++)
497     if (!svn_ctype_isdigit(*p))
498       return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
499         _("%s file '%s' contains unexpected non-digit '%c' within '%s'"),
500         title, svn_dirent_local_style(path, scratch_pool), *p, buf);
501
502   return SVN_NO_ERROR;
503 }
504
505 svn_error_t *
506 svn_fs_x__read_min_unpacked_rev(svn_revnum_t *min_unpacked_rev,
507                                 svn_fs_t *fs,
508                                 apr_pool_t *scratch_pool)
509 {
510   char buf[80];
511   apr_file_t *file;
512   apr_size_t len;
513
514   SVN_ERR(svn_io_file_open(&file,
515                            svn_fs_x__path_min_unpacked_rev(fs, scratch_pool),
516                            APR_READ | APR_BUFFERED,
517                            APR_OS_DEFAULT,
518                            scratch_pool));
519   len = sizeof(buf);
520   SVN_ERR(svn_io_read_length_line(file, buf, &len, scratch_pool));
521   SVN_ERR(svn_io_file_close(file, scratch_pool));
522
523   SVN_ERR(svn_revnum_parse(min_unpacked_rev, buf, NULL));
524   return SVN_NO_ERROR;
525 }
526
527 svn_error_t *
528 svn_fs_x__update_min_unpacked_rev(svn_fs_t *fs,
529                                   apr_pool_t *scratch_pool)
530 {
531   svn_fs_x__data_t *ffd = fs->fsap_data;
532   return svn_fs_x__read_min_unpacked_rev(&ffd->min_unpacked_rev, fs,
533                                          scratch_pool);
534 }
535
536 /* Write a file FILENAME in directory FS_PATH, containing a single line
537  * with the number REVNUM in ASCII decimal.  Move the file into place
538  * atomically, overwriting any existing file.
539  *
540  * Similar to write_current(). */
541 svn_error_t *
542 svn_fs_x__write_min_unpacked_rev(svn_fs_t *fs,
543                                  svn_revnum_t revnum,
544                                  apr_pool_t *scratch_pool)
545 {
546   const char *final_path;
547   char buf[SVN_INT64_BUFFER_SIZE];
548   apr_size_t len = svn__i64toa(buf, revnum);
549   buf[len] = '\n';
550
551   final_path = svn_fs_x__path_min_unpacked_rev(fs, scratch_pool);
552
553   SVN_ERR(svn_io_write_atomic(final_path, buf, len + 1,
554                               final_path /* copy_perms */, scratch_pool));
555
556   return SVN_NO_ERROR;
557 }
558
559 svn_error_t *
560 svn_fs_x__read_current(svn_revnum_t *rev,
561                        svn_fs_t *fs,
562                        apr_pool_t *scratch_pool)
563 {
564   const char *str;
565   svn_stringbuf_t *content;
566   SVN_ERR(svn_fs_x__read_content(&content,
567                                  svn_fs_x__path_current(fs, scratch_pool),
568                                  scratch_pool));
569   SVN_ERR(svn_revnum_parse(rev, content->data, &str));
570   if (*str != '\n')
571     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
572                             _("Corrupt 'current' file"));
573
574   return SVN_NO_ERROR;
575 }
576
577 /* Atomically update the 'current' file to hold the specifed REV.
578    Perform temporary allocations in SCRATCH_POOL. */
579 svn_error_t *
580 svn_fs_x__write_current(svn_fs_t *fs,
581                         svn_revnum_t rev,
582                         apr_pool_t *scratch_pool)
583 {
584   char *buf;
585   const char *tmp_name, *name;
586
587   /* Now we can just write out this line. */
588   buf = apr_psprintf(scratch_pool, "%ld\n", rev);
589
590   name = svn_fs_x__path_current(fs, scratch_pool);
591   SVN_ERR(svn_io_write_unique(&tmp_name,
592                               svn_dirent_dirname(name, scratch_pool),
593                               buf, strlen(buf),
594                               svn_io_file_del_none, scratch_pool));
595
596   return svn_fs_x__move_into_place(tmp_name, name, name, scratch_pool);
597 }
598
599
600 svn_error_t *
601 svn_fs_x__try_stringbuf_from_file(svn_stringbuf_t **content,
602                                   svn_boolean_t *missing,
603                                   const char *path,
604                                   svn_boolean_t last_attempt,
605                                   apr_pool_t *result_pool)
606 {
607   svn_error_t *err = svn_stringbuf_from_file2(content, path, result_pool);
608   if (missing)
609     *missing = FALSE;
610
611   if (err)
612     {
613       *content = NULL;
614
615       if (APR_STATUS_IS_ENOENT(err->apr_err))
616         {
617           if (!last_attempt)
618             {
619               svn_error_clear(err);
620               if (missing)
621                 *missing = TRUE;
622               return SVN_NO_ERROR;
623             }
624         }
625 #ifdef ESTALE
626       else if (APR_TO_OS_ERROR(err->apr_err) == ESTALE
627                 || APR_TO_OS_ERROR(err->apr_err) == EIO)
628         {
629           if (!last_attempt)
630             {
631               svn_error_clear(err);
632               return SVN_NO_ERROR;
633             }
634         }
635 #endif
636     }
637
638   return svn_error_trace(err);
639 }
640
641 /* Fetch the current offset of FILE into *OFFSET_P. */
642 svn_error_t *
643 svn_fs_x__get_file_offset(apr_off_t *offset_p,
644                           apr_file_t *file,
645                           apr_pool_t *scratch_pool)
646 {
647   apr_off_t offset;
648
649   /* Note that, for buffered files, one (possibly surprising) side-effect
650      of this call is to flush any unwritten data to disk. */
651   offset = 0;
652   SVN_ERR(svn_io_file_seek(file, APR_CUR, &offset, scratch_pool));
653   *offset_p = offset;
654
655   return SVN_NO_ERROR;
656 }
657
658 svn_error_t *
659 svn_fs_x__read_content(svn_stringbuf_t **content,
660                        const char *fname,
661                        apr_pool_t *result_pool)
662 {
663   int i;
664   *content = NULL;
665
666   for (i = 0; !*content && (i < SVN_FS_X__RECOVERABLE_RETRY_COUNT); ++i)
667     SVN_ERR(svn_fs_x__try_stringbuf_from_file(content, NULL,
668                            fname, i + 1 < SVN_FS_X__RECOVERABLE_RETRY_COUNT,
669                            result_pool));
670
671   if (!*content)
672     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
673                              _("Can't read '%s'"),
674                              svn_dirent_local_style(fname, result_pool));
675
676   return SVN_NO_ERROR;
677 }
678
679 /* Reads a line from STREAM and converts it to a 64 bit integer to be
680  * returned in *RESULT.  If we encounter eof, set *HIT_EOF and leave
681  * *RESULT unchanged.  If HIT_EOF is NULL, EOF causes an "corrupt FS"
682  * error return.
683  * SCRATCH_POOL is used for temporary allocations.
684  */
685 svn_error_t *
686 svn_fs_x__read_number_from_stream(apr_int64_t *result,
687                                   svn_boolean_t *hit_eof,
688                                   svn_stream_t *stream,
689                                   apr_pool_t *scratch_pool)
690 {
691   svn_stringbuf_t *sb;
692   svn_boolean_t eof;
693   svn_error_t *err;
694
695   SVN_ERR(svn_stream_readline(stream, &sb, "\n", &eof, scratch_pool));
696   if (hit_eof)
697     *hit_eof = eof;
698   else
699     if (eof)
700       return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, _("Unexpected EOF"));
701
702   if (!eof)
703     {
704       err = svn_cstring_atoi64(result, sb->data);
705       if (err)
706         return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
707                                  _("Number '%s' invalid or too large"),
708                                  sb->data);
709     }
710
711   return SVN_NO_ERROR;
712 }
713
714
715 /* Move a file into place from OLD_FILENAME in the transactions
716    directory to its final location NEW_FILENAME in the repository.  On
717    Unix, match the permissions of the new file to the permissions of
718    PERMS_REFERENCE.  Temporary allocations are from SCRATCH_POOL.
719
720    This function almost duplicates svn_io_file_move(), but it tries to
721    guarantee a flush. */
722 svn_error_t *
723 svn_fs_x__move_into_place(const char *old_filename,
724                           const char *new_filename,
725                           const char *perms_reference,
726                           apr_pool_t *scratch_pool)
727 {
728   svn_error_t *err;
729
730   SVN_ERR(svn_io_copy_perms(perms_reference, old_filename, scratch_pool));
731
732   /* Move the file into place. */
733   err = svn_io_file_rename(old_filename, new_filename, scratch_pool);
734   if (err && APR_STATUS_IS_EXDEV(err->apr_err))
735     {
736       apr_file_t *file;
737
738       /* Can't rename across devices; fall back to copying. */
739       svn_error_clear(err);
740       err = SVN_NO_ERROR;
741       SVN_ERR(svn_io_copy_file(old_filename, new_filename, TRUE,
742                                scratch_pool));
743
744       /* Flush the target of the copy to disk. */
745       SVN_ERR(svn_io_file_open(&file, new_filename, APR_READ,
746                                APR_OS_DEFAULT, scratch_pool));
747       /* ### BH: Does this really guarantee a flush of the data written
748          ### via a completely different handle on all operating systems?
749          ###
750          ### Maybe we should perform the copy ourselves instead of making
751          ### apr do that and flush the real handle? */
752       SVN_ERR(svn_io_file_flush_to_disk(file, scratch_pool));
753       SVN_ERR(svn_io_file_close(file, scratch_pool));
754     }
755   if (err)
756     return svn_error_trace(err);
757
758 #ifdef __linux__
759   {
760     /* Linux has the unusual feature that fsync() on a file is not
761        enough to ensure that a file's directory entries have been
762        flushed to disk; you have to fsync the directory as well.
763        On other operating systems, we'd only be asking for trouble
764        by trying to open and fsync a directory. */
765     const char *dirname;
766     apr_file_t *file;
767
768     dirname = svn_dirent_dirname(new_filename, scratch_pool);
769     SVN_ERR(svn_io_file_open(&file, dirname, APR_READ, APR_OS_DEFAULT,
770                              scratch_pool));
771     SVN_ERR(svn_io_file_flush_to_disk(file, scratch_pool));
772     SVN_ERR(svn_io_file_close(file, scratch_pool));
773   }
774 #endif
775
776   return SVN_NO_ERROR;
777 }