1
/* util.c --- utility functions for FSX repo access
3
* ====================================================================
4
* Licensed to the Apache Software Foundation (ASF) under one
5
* or more contributor license agreements. See the NOTICE file
6
* distributed with this work for additional information
7
* regarding copyright ownership. The ASF licenses this file
8
* to you under the Apache License, Version 2.0 (the
9
* "License"); you may not use this file except in compliance
10
* with the License. You may obtain a copy of the License at
12
* http://www.apache.org/licenses/LICENSE-2.0
14
* Unless required by applicable law or agreed to in writing,
15
* software distributed under the License is distributed on an
16
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17
* KIND, either express or implied. See the License for the
18
* specific language governing permissions and limitations
20
* ====================================================================
25
#include "svn_ctype.h"
26
#include "svn_dirent_uri.h"
27
#include "private/svn_string_private.h"
33
#include "../libsvn_fs/fs-loader.h"
35
#include "svn_private_config.h"
37
/* Following are defines that specify the textual elements of the
38
native filesystem directories and revision files. */
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
47
I didn't keep track of pool lifetimes at all in this code. There
48
are likely some errors because of that.
52
/* Pathname helper functions */
54
/* Return TRUE is REV is packed in FS, FALSE otherwise. */
56
svn_fs_x__is_packed_rev(svn_fs_t *fs, svn_revnum_t rev)
58
svn_fs_x__data_t *ffd = fs->fsap_data;
60
return (rev < ffd->min_unpacked_rev);
63
/* Return TRUE is REV is packed in FS, FALSE otherwise. */
65
svn_fs_x__is_packed_revprop(svn_fs_t *fs, svn_revnum_t rev)
67
svn_fs_x__data_t *ffd = fs->fsap_data;
69
/* rev 0 will not be packed */
70
return (rev < ffd->min_unpacked_rev) && (rev != 0);
74
svn_fs_x__packed_base_rev(svn_fs_t *fs, svn_revnum_t rev)
76
svn_fs_x__data_t *ffd = fs->fsap_data;
78
return rev < ffd->min_unpacked_rev
79
? rev - (rev % ffd->max_files_per_dir)
84
svn_fs_x__pack_size(svn_fs_t *fs, svn_revnum_t rev)
86
svn_fs_x__data_t *ffd = fs->fsap_data;
88
return rev < ffd->min_unpacked_rev ? ffd->max_files_per_dir : 1;
92
svn_fs_x__path_format(svn_fs_t *fs,
93
apr_pool_t *result_pool)
95
return svn_dirent_join(fs->path, PATH_FORMAT, result_pool);
99
svn_fs_x__path_uuid(svn_fs_t *fs,
100
apr_pool_t *result_pool)
102
return svn_dirent_join(fs->path, PATH_UUID, result_pool);
106
svn_fs_x__path_current(svn_fs_t *fs,
107
apr_pool_t *result_pool)
109
return svn_dirent_join(fs->path, PATH_CURRENT, result_pool);
113
svn_fs_x__path_txn_current(svn_fs_t *fs,
114
apr_pool_t *result_pool)
116
return svn_dirent_join(fs->path, PATH_TXN_CURRENT,
121
svn_fs_x__path_txn_current_lock(svn_fs_t *fs,
122
apr_pool_t *result_pool)
124
return svn_dirent_join(fs->path, PATH_TXN_CURRENT_LOCK, result_pool);
128
svn_fs_x__path_lock(svn_fs_t *fs,
129
apr_pool_t *result_pool)
131
return svn_dirent_join(fs->path, PATH_LOCK_FILE, result_pool);
135
svn_fs_x__path_pack_lock(svn_fs_t *fs,
136
apr_pool_t *result_pool)
138
return svn_dirent_join(fs->path, PATH_PACK_LOCK_FILE, result_pool);
142
svn_fs_x__path_revprop_generation(svn_fs_t *fs,
143
apr_pool_t *result_pool)
145
return svn_dirent_join(fs->path, PATH_REVPROP_GENERATION, result_pool);
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.
153
* Allocate the result in RESULT_POOL.
155
construct_shard_sub_path(svn_fs_t *fs,
157
svn_boolean_t revprops,
158
svn_boolean_t packed,
159
const char *filename,
160
apr_pool_t *result_pool)
162
svn_fs_x__data_t *ffd = fs->fsap_data;
163
char buffer[SVN_INT64_BUFFER_SIZE + sizeof(PATH_EXT_PACKED_SHARD)] = { 0 };
165
/* Select the appropriate parent path constant. */
166
const char *parent = revprops ? PATH_REVPROPS_DIR : PATH_REVS_DIR;
168
/* String containing the shard number. */
169
apr_size_t len = svn__i64toa(buffer, rev / ffd->max_files_per_dir);
171
/* Append the suffix. Limit it to the buffer size (should never hit it). */
173
strncpy(buffer + len, PATH_EXT_PACKED_SHARD, sizeof(buffer) - len - 1);
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);
181
svn_fs_x__path_rev_packed(svn_fs_t *fs,
184
apr_pool_t *result_pool)
186
assert(svn_fs_x__is_packed_rev(fs, rev));
187
return construct_shard_sub_path(fs, rev, FALSE, TRUE, kind, result_pool);
191
svn_fs_x__path_rev_shard(svn_fs_t *fs,
193
apr_pool_t *result_pool)
195
return construct_shard_sub_path(fs, rev, FALSE, FALSE, NULL, result_pool);
199
svn_fs_x__path_rev(svn_fs_t *fs,
201
apr_pool_t *result_pool)
203
char buffer[SVN_INT64_BUFFER_SIZE];
204
svn__i64toa(buffer, rev);
206
assert(! svn_fs_x__is_packed_rev(fs, rev));
207
return construct_shard_sub_path(fs, rev, FALSE, FALSE, buffer, result_pool);
211
svn_fs_x__path_rev_absolute(svn_fs_t *fs,
213
apr_pool_t *result_pool)
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);
221
svn_fs_x__path_revprops_shard(svn_fs_t *fs,
223
apr_pool_t *result_pool)
225
return construct_shard_sub_path(fs, rev, TRUE, FALSE, NULL, result_pool);
229
svn_fs_x__path_revprops_pack_shard(svn_fs_t *fs,
231
apr_pool_t *result_pool)
233
return construct_shard_sub_path(fs, rev, TRUE, TRUE, NULL, result_pool);
237
svn_fs_x__path_revprops(svn_fs_t *fs,
239
apr_pool_t *result_pool)
241
char buffer[SVN_INT64_BUFFER_SIZE];
242
svn__i64toa(buffer, rev);
244
assert(! svn_fs_x__is_packed_revprop(fs, rev));
245
return construct_shard_sub_path(fs, rev, TRUE, FALSE, buffer, result_pool);
249
svn_fs_x__txn_name(svn_fs_x__txn_id_t txn_id,
250
apr_pool_t *result_pool)
252
char *p = apr_palloc(result_pool, SVN_INT64_BUFFER_SIZE);
253
svn__ui64tobase36(p, txn_id);
258
svn_fs_x__txn_by_name(svn_fs_x__txn_id_t *txn_id,
259
const char *txn_name)
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);
272
svn_fs_x__path_txns_dir(svn_fs_t *fs,
273
apr_pool_t *result_pool)
275
return svn_dirent_join(fs->path, PATH_TXNS_DIR, result_pool);
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
282
* Allocate the result in RESULT_POOL.
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)
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);
295
/* If FILENAME is NULL, it will terminate the list of segments
297
return svn_dirent_join_many(result_pool, fs->path, PATH_TXNS_DIR,
298
buffer, filename, SVN_VA_NULL);
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)
306
return construct_txn_path(fs, txn_id, NULL, result_pool);
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.
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,
318
svn_checksum_t checksum;
319
checksum.digest = sha1;
320
checksum.kind = svn_checksum_sha1;
322
return svn_dirent_join(svn_fs_x__path_txn_dir(fs, txn_id, pool),
323
svn_checksum_to_cstring(&checksum, pool),
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)
332
return construct_txn_path(fs, txn_id, PATH_CHANGES, result_pool);
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)
340
return construct_txn_path(fs, txn_id, PATH_TXN_PROPS, result_pool);
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)
348
return construct_txn_path(fs, txn_id, PATH_TXN_PROPS_FINAL, result_pool);
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)
356
return construct_txn_path(fs, txn_id, PATH_INDEX PATH_EXT_L2P_INDEX,
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)
365
return construct_txn_path(fs, txn_id, PATH_INDEX PATH_EXT_P2L_INDEX,
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)
374
return construct_txn_path(fs, txn_id, PATH_NEXT_IDS, result_pool);
378
svn_fs_x__path_min_unpacked_rev(svn_fs_t *fs,
379
apr_pool_t *result_pool)
381
return svn_dirent_join(fs->path, PATH_MIN_UNPACKED_REV, result_pool);
385
svn_fs_x__path_txn_proto_revs(svn_fs_t *fs,
386
apr_pool_t *result_pool)
388
return svn_dirent_join(fs->path, PATH_TXN_PROTOS_DIR, result_pool);
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)
396
return construct_txn_path(fs, txn_id, PATH_TXN_ITEM_INDEX, result_pool);
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.
402
* Allocate the result in RESULT_POOL.
405
construct_proto_rev_path(svn_fs_t *fs,
406
svn_fs_x__txn_id_t txn_id,
408
apr_pool_t *result_pool)
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);
415
/* If FILENAME is NULL, it will terminate the list of segments
417
return svn_dirent_join_many(result_pool, fs->path, PATH_TXN_PROTOS_DIR,
418
buffer, SVN_VA_NULL);
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)
426
return construct_proto_rev_path(fs, txn_id, PATH_EXT_REV, result_pool);
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)
434
return construct_proto_rev_path(fs, txn_id, PATH_EXT_REV_LOCK, result_pool);
437
/* Return the full path of the noderev-related file with the extension SUFFIX
438
* for noderev *ID in transaction TXN_ID in FS.
440
* Allocate the result in RESULT_POOL and temporaries in SCRATCH_POOL.
443
construct_txn_node_path(svn_fs_t *fs,
444
const svn_fs_x__id_t *id,
446
apr_pool_t *result_pool,
447
apr_pool_t *scratch_pool)
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);
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",
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)
464
return construct_txn_node_path(fs, id, "", result_pool, scratch_pool);
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)
473
return construct_txn_node_path(fs, id, PATH_EXT_PROPS, result_pool,
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)
483
return construct_txn_node_path(fs, id, PATH_EXT_CHILDREN, result_pool,
488
svn_fs_x__check_file_buffer_numeric(const char *buf,
492
apr_pool_t *scratch_pool)
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);
506
svn_fs_x__read_min_unpacked_rev(svn_revnum_t *min_unpacked_rev,
508
apr_pool_t *scratch_pool)
514
SVN_ERR(svn_io_file_open(&file,
515
svn_fs_x__path_min_unpacked_rev(fs, scratch_pool),
516
APR_READ | APR_BUFFERED,
520
SVN_ERR(svn_io_read_length_line(file, buf, &len, scratch_pool));
521
SVN_ERR(svn_io_file_close(file, scratch_pool));
523
SVN_ERR(svn_revnum_parse(min_unpacked_rev, buf, NULL));
528
svn_fs_x__update_min_unpacked_rev(svn_fs_t *fs,
529
apr_pool_t *scratch_pool)
531
svn_fs_x__data_t *ffd = fs->fsap_data;
532
return svn_fs_x__read_min_unpacked_rev(&ffd->min_unpacked_rev, fs,
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.
540
* Similar to write_current(). */
542
svn_fs_x__write_min_unpacked_rev(svn_fs_t *fs,
544
apr_pool_t *scratch_pool)
546
const char *final_path;
547
char buf[SVN_INT64_BUFFER_SIZE];
548
apr_size_t len = svn__i64toa(buf, revnum);
551
final_path = svn_fs_x__path_min_unpacked_rev(fs, scratch_pool);
553
SVN_ERR(svn_io_write_atomic(final_path, buf, len + 1,
554
final_path /* copy_perms */, scratch_pool));
560
svn_fs_x__read_current(svn_revnum_t *rev,
562
apr_pool_t *scratch_pool)
565
svn_stringbuf_t *content;
566
SVN_ERR(svn_fs_x__read_content(&content,
567
svn_fs_x__path_current(fs, scratch_pool),
569
SVN_ERR(svn_revnum_parse(rev, content->data, &str));
571
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
572
_("Corrupt 'current' file"));
577
/* Atomically update the 'current' file to hold the specifed REV.
578
Perform temporary allocations in SCRATCH_POOL. */
580
svn_fs_x__write_current(svn_fs_t *fs,
582
apr_pool_t *scratch_pool)
585
const char *tmp_name, *name;
587
/* Now we can just write out this line. */
588
buf = apr_psprintf(scratch_pool, "%ld\n", rev);
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),
594
svn_io_file_del_none, scratch_pool));
596
return svn_fs_x__move_into_place(tmp_name, name, name, scratch_pool);
601
svn_fs_x__try_stringbuf_from_file(svn_stringbuf_t **content,
602
svn_boolean_t *missing,
604
svn_boolean_t last_attempt,
605
apr_pool_t *result_pool)
607
svn_error_t *err = svn_stringbuf_from_file2(content, path, result_pool);
615
if (APR_STATUS_IS_ENOENT(err->apr_err))
619
svn_error_clear(err);
626
else if (APR_TO_OS_ERROR(err->apr_err) == ESTALE
627
|| APR_TO_OS_ERROR(err->apr_err) == EIO)
631
svn_error_clear(err);
638
return svn_error_trace(err);
641
/* Fetch the current offset of FILE into *OFFSET_P. */
643
svn_fs_x__get_file_offset(apr_off_t *offset_p,
645
apr_pool_t *scratch_pool)
649
/* Note that, for buffered files, one (possibly surprising) side-effect
650
of this call is to flush any unwritten data to disk. */
652
SVN_ERR(svn_io_file_seek(file, APR_CUR, &offset, scratch_pool));
659
svn_fs_x__read_content(svn_stringbuf_t **content,
661
apr_pool_t *result_pool)
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,
672
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
673
_("Can't read '%s'"),
674
svn_dirent_local_style(fname, result_pool));
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"
683
* SCRATCH_POOL is used for temporary allocations.
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)
695
SVN_ERR(svn_stream_readline(stream, &sb, "\n", &eof, scratch_pool));
700
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, _("Unexpected EOF"));
704
err = svn_cstring_atoi64(result, sb->data);
706
return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
707
_("Number '%s' invalid or too large"),
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.
720
This function almost duplicates svn_io_file_move(), but it tries to
721
guarantee a flush. */
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)
730
SVN_ERR(svn_io_copy_perms(perms_reference, old_filename, scratch_pool));
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))
738
/* Can't rename across devices; fall back to copying. */
739
svn_error_clear(err);
741
SVN_ERR(svn_io_copy_file(old_filename, new_filename, TRUE,
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?
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));
756
return svn_error_trace(err);
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. */
768
dirname = svn_dirent_dirname(new_filename, scratch_pool);
769
SVN_ERR(svn_io_file_open(&file, dirname, APR_READ, APR_OS_DEFAULT,
771
SVN_ERR(svn_io_file_flush_to_disk(file, scratch_pool));
772
SVN_ERR(svn_io_file_close(file, scratch_pool));