/* util.c --- utility functions for FSX repo access
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*/
#include <assert.h>
#include "svn_ctype.h"
#include "svn_dirent_uri.h"
#include "private/svn_string_private.h"
#include "fs_x.h"
#include "id.h"
#include "util.h"
#include "../libsvn_fs/fs-loader.h"
#include "svn_private_config.h"
/* Following are defines that specify the textual elements of the
native filesystem directories and revision files. */
/* Notes:
To avoid opening and closing the rev-files all the time, it would
probably be advantageous to keep each rev-file open for the
lifetime of the transaction object. I'll leave that as a later
optimization for now.
I didn't keep track of pool lifetimes at all in this code. There
are likely some errors because of that.
*/
/* Pathname helper functions */
/* Return TRUE is REV is packed in FS, FALSE otherwise. */
svn_boolean_t
svn_fs_x__is_packed_rev(svn_fs_t *fs, svn_revnum_t rev)
{
svn_fs_x__data_t *ffd = fs->fsap_data;
return (rev < ffd->min_unpacked_rev);
}
/* Return TRUE is REV is packed in FS, FALSE otherwise. */
svn_boolean_t
svn_fs_x__is_packed_revprop(svn_fs_t *fs, svn_revnum_t rev)
{
svn_fs_x__data_t *ffd = fs->fsap_data;
/* rev 0 will not be packed */
return (rev < ffd->min_unpacked_rev) && (rev != 0);
}
svn_revnum_t
svn_fs_x__packed_base_rev(svn_fs_t *fs, svn_revnum_t rev)
{
svn_fs_x__data_t *ffd = fs->fsap_data;
return rev < ffd->min_unpacked_rev
? rev - (rev % ffd->max_files_per_dir)
: rev;
}
svn_revnum_t
svn_fs_x__pack_size(svn_fs_t *fs, svn_revnum_t rev)
{
svn_fs_x__data_t *ffd = fs->fsap_data;
return rev < ffd->min_unpacked_rev ? ffd->max_files_per_dir : 1;
}
const char *
svn_fs_x__path_format(svn_fs_t *fs,
apr_pool_t *result_pool)
{
return svn_dirent_join(fs->path, PATH_FORMAT, result_pool);
}
const char *
svn_fs_x__path_uuid(svn_fs_t *fs,
apr_pool_t *result_pool)
{
return svn_dirent_join(fs->path, PATH_UUID, result_pool);
}
const char *
svn_fs_x__path_current(svn_fs_t *fs,
apr_pool_t *result_pool)
{
return svn_dirent_join(fs->path, PATH_CURRENT, result_pool);
}
const char *
svn_fs_x__path_next(svn_fs_t *fs,
apr_pool_t *result_pool)
{
return svn_dirent_join(fs->path, PATH_NEXT, result_pool);
}
const char *
svn_fs_x__path_txn_current(svn_fs_t *fs,
apr_pool_t *result_pool)
{
return svn_dirent_join(fs->path, PATH_TXN_CURRENT, result_pool);
}
const char *
svn_fs_x__path_txn_current_lock(svn_fs_t *fs,
apr_pool_t *result_pool)
{
return svn_dirent_join(fs->path, PATH_TXN_CURRENT_LOCK, result_pool);
}
const char *
svn_fs_x__path_lock(svn_fs_t *fs,
apr_pool_t *result_pool)
{
return svn_dirent_join(fs->path, PATH_LOCK_FILE, result_pool);
}
const char *
svn_fs_x__path_pack_lock(svn_fs_t *fs,
apr_pool_t *result_pool)
{
return svn_dirent_join(fs->path, PATH_PACK_LOCK_FILE, result_pool);
}
const char *
svn_fs_x__path_revprop_generation(svn_fs_t *fs,
apr_pool_t *result_pool)
{
return svn_dirent_join(fs->path, PATH_REVPROP_GENERATION, result_pool);
}
/* Return the full path of the file FILENAME within revision REV's shard in
* FS. If FILENAME is NULL, return the shard directory directory itself.
* PACKED says whether we want the packed shard's name.
*
* Allocate the result in RESULT_POOL.
*/static const char*
construct_shard_sub_path(svn_fs_t *fs,
svn_revnum_t rev,
svn_boolean_t packed,
const char *filename,
apr_pool_t *result_pool)
{
svn_fs_x__data_t *ffd = fs->fsap_data;
char buffer[SVN_INT64_BUFFER_SIZE + sizeof(PATH_EXT_PACKED_SHARD)] = { 0 };
/* String containing the shard number. */
apr_size_t len = svn__i64toa(buffer, rev / ffd->max_files_per_dir);
/* Append the suffix. Limit it to the buffer size (should never hit it). */
if (packed)
strncpy(buffer + len, PATH_EXT_PACKED_SHARD, sizeof(buffer) - len - 1);
/* This will also work for NULL FILENAME as well. */
return svn_dirent_join_many(result_pool, fs->path, PATH_REVS_DIR, buffer,
filename, SVN_VA_NULL);
}
const char *
svn_fs_x__path_rev_packed(svn_fs_t *fs,
svn_revnum_t rev,
const char *kind,
apr_pool_t *result_pool)
{
assert(svn_fs_x__is_packed_rev(fs, rev));
return construct_shard_sub_path(fs, rev, TRUE, kind, result_pool);
}
const char *
svn_fs_x__path_shard(svn_fs_t *fs,
svn_revnum_t rev,
apr_pool_t *result_pool)
{
return construct_shard_sub_path(fs, rev, FALSE, NULL, result_pool);
}
const char *
svn_fs_x__path_rev(svn_fs_t *fs,
svn_revnum_t rev,
apr_pool_t *result_pool)
{
char buffer[SVN_INT64_BUFFER_SIZE + 1];
buffer[0] = 'r';
svn__i64toa(buffer + 1, rev);
assert(! svn_fs_x__is_packed_rev(fs, rev));
return construct_shard_sub_path(fs, rev, FALSE, buffer, result_pool);
}
const char *
svn_fs_x__path_rev_absolute(svn_fs_t *fs,
svn_revnum_t rev,
apr_pool_t *result_pool)
{
return svn_fs_x__is_packed_rev(fs, rev)
? svn_fs_x__path_rev_packed(fs, rev, PATH_PACKED, result_pool)
: svn_fs_x__path_rev(fs, rev, result_pool);
}
const char *
svn_fs_x__path_pack_shard(svn_fs_t *fs,
svn_revnum_t rev,
apr_pool_t *result_pool)
{
return construct_shard_sub_path(fs, rev, TRUE, NULL, result_pool);
}
const char *
svn_fs_x__path_revprops(svn_fs_t *fs,
svn_revnum_t rev,
apr_pool_t *result_pool)
{
char buffer[SVN_INT64_BUFFER_SIZE + 1];
buffer[0] = 'p';
svn__i64toa(buffer + 1, rev);
assert(! svn_fs_x__is_packed_revprop(fs, rev));
/* Revprops for packed r0 are not packed, yet stored in the packed shard.
Hence, the second flag must check for packed _rev_ - not revprop. */
return construct_shard_sub_path(fs, rev,
svn_fs_x__is_packed_rev(fs, rev) /* sic! */,
buffer, result_pool);
}
const char *
svn_fs_x__txn_name(svn_fs_x__txn_id_t txn_id,
apr_pool_t *result_pool)
{
char *p = apr_palloc(result_pool, SVN_INT64_BUFFER_SIZE);
svn__ui64tobase36(p, txn_id);
return p;
}
svn_error_t *
svn_fs_x__txn_by_name(svn_fs_x__txn_id_t *txn_id,
const char *txn_name)
{
const char *next;
apr_uint64_t id = svn__base36toui64(&next, txn_name);
if (next == NULL || *next != 0 || *txn_name == 0)
return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
"Malformed TXN name '%s'", txn_name);
*txn_id = id;
return SVN_NO_ERROR;
}
const char *
svn_fs_x__path_txns_dir(svn_fs_t *fs,
apr_pool_t *result_pool)
{
return svn_dirent_join(fs->path, PATH_TXNS_DIR, result_pool);
}
/* Return the full path of the file FILENAME within transaction TXN_ID's
* transaction directory in FS. If FILENAME is NULL, return the transaction
* directory itself.
*
* Allocate the result in RESULT_POOL.
*/
static const char *
construct_txn_path(svn_fs_t *fs,
svn_fs_x__txn_id_t txn_id,
const char *filename,
apr_pool_t *result_pool)
{
/* Construct the transaction directory name without temp. allocations. */
char buffer[SVN_INT64_BUFFER_SIZE + sizeof(PATH_EXT_TXN)] = { 0 };
apr_size_t len = svn__ui64tobase36(buffer, txn_id);
strncpy(buffer + len, PATH_EXT_TXN, sizeof(buffer) - len - 1);
/* If FILENAME is NULL, it will terminate the list of segments
to concatenate. */
return svn_dirent_join_many(result_pool, fs->path, PATH_TXNS_DIR,
buffer, filename, SVN_VA_NULL);
}
const char *
svn_fs_x__path_txn_dir(svn_fs_t *fs,
svn_fs_x__txn_id_t txn_id,
apr_pool_t *result_pool)
{
return construct_txn_path(fs, txn_id, NULL, result_pool);
}
/* Return the name of the sha1->rep mapping file in transaction TXN_ID
* within FS for the given SHA1 checksum. Use POOL for allocations.
*/
const char *
svn_fs_x__path_txn_sha1(svn_fs_t *fs,
svn_fs_x__txn_id_t txn_id,
const unsigned char *sha1,
apr_pool_t *pool)
{
svn_checksum_t checksum;
checksum.digest = sha1;
checksum.kind = svn_checksum_sha1;
return svn_dirent_join(svn_fs_x__path_txn_dir(fs, txn_id, pool),
svn_checksum_to_cstring(&checksum, pool),
pool);
}
const char *
svn_fs_x__path_txn_changes(svn_fs_t *fs,
svn_fs_x__txn_id_t txn_id,
apr_pool_t *result_pool)
{
return construct_txn_path(fs, txn_id, PATH_CHANGES, result_pool);
}
const char *
svn_fs_x__path_txn_props(svn_fs_t *fs,
svn_fs_x__txn_id_t txn_id,
apr_pool_t *result_pool)
{
return construct_txn_path(fs, txn_id, PATH_TXN_PROPS, result_pool);
}
const char*
svn_fs_x__path_l2p_proto_index(svn_fs_t *fs,
svn_fs_x__txn_id_t txn_id,
apr_pool_t *result_pool)
{
return construct_txn_path(fs, txn_id, PATH_INDEX PATH_EXT_L2P_INDEX,
result_pool);
}
const char*
svn_fs_x__path_p2l_proto_index(svn_fs_t *fs,
svn_fs_x__txn_id_t txn_id,
apr_pool_t *result_pool)
{
return construct_txn_path(fs, txn_id, PATH_INDEX PATH_EXT_P2L_INDEX,
result_pool);
}
const char *
svn_fs_x__path_txn_next_ids(svn_fs_t *fs,
svn_fs_x__txn_id_t txn_id,
apr_pool_t *result_pool)
{
return construct_txn_path(fs, txn_id, PATH_NEXT_IDS, result_pool);
}
const char *
svn_fs_x__path_min_unpacked_rev(svn_fs_t *fs,
apr_pool_t *result_pool)
{
return svn_dirent_join(fs->path, PATH_MIN_UNPACKED_REV, result_pool);
}
const char *
svn_fs_x__path_txn_proto_revs(svn_fs_t *fs,
apr_pool_t *result_pool)
{
return svn_dirent_join(fs->path, PATH_TXN_PROTOS_DIR, result_pool);
}
const char *
svn_fs_x__path_txn_item_index(svn_fs_t *fs,
svn_fs_x__txn_id_t txn_id,
apr_pool_t *result_pool)
{
return construct_txn_path(fs, txn_id, PATH_TXN_ITEM_INDEX, result_pool);
}
/* Return the full path of the proto-rev file / lock file for transaction
* TXN_ID in FS. The SUFFIX determines what file (rev / lock) it will be.
*
* Allocate the result in RESULT_POOL.
*/
static const char *
construct_proto_rev_path(svn_fs_t *fs,
svn_fs_x__txn_id_t txn_id,
const char *suffix,
apr_pool_t *result_pool)
{
/* Construct the file name without temp. allocations. */
char buffer[SVN_INT64_BUFFER_SIZE + sizeof(PATH_EXT_REV_LOCK)] = { 0 };
apr_size_t len = svn__ui64tobase36(buffer, txn_id);
strncpy(buffer + len, suffix, sizeof(buffer) - len - 1);
/* If FILENAME is NULL, it will terminate the list of segments
to concatenate. */
return svn_dirent_join_many(result_pool, fs->path, PATH_TXN_PROTOS_DIR,
buffer, SVN_VA_NULL);
}
const char *
svn_fs_x__path_txn_proto_rev(svn_fs_t *fs,
svn_fs_x__txn_id_t txn_id,
apr_pool_t *result_pool)
{
return construct_proto_rev_path(fs, txn_id, PATH_EXT_REV, result_pool);
}
const char *
svn_fs_x__path_txn_proto_rev_lock(svn_fs_t *fs,
svn_fs_x__txn_id_t txn_id,
apr_pool_t *result_pool)
{
return construct_proto_rev_path(fs, txn_id, PATH_EXT_REV_LOCK, result_pool);
}
/* Return the full path of the noderev-related file with the extension SUFFIX
* for noderev *ID in transaction TXN_ID in FS.
*
* Allocate the result in RESULT_POOL and temporaries in SCRATCH_POOL.
*/
static const char *
construct_txn_node_path(svn_fs_t *fs,
const svn_fs_x__id_t *id,
const char *suffix,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
const char *filename = svn_fs_x__id_unparse(id, result_pool)->data;
apr_int64_t txn_id = svn_fs_x__get_txn_id(id->change_set);
return svn_dirent_join(svn_fs_x__path_txn_dir(fs, txn_id, scratch_pool),
apr_psprintf(scratch_pool, PATH_PREFIX_NODE "%s%s",
filename, suffix),
result_pool);
}
const char *
svn_fs_x__path_txn_node_rev(svn_fs_t *fs,
const svn_fs_x__id_t *id,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
return construct_txn_node_path(fs, id, "", result_pool, scratch_pool);
}
const char *
svn_fs_x__path_txn_node_props(svn_fs_t *fs,
const svn_fs_x__id_t *id,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
return construct_txn_node_path(fs, id, PATH_EXT_PROPS, result_pool,
scratch_pool);
}
const char *
svn_fs_x__path_txn_node_children(svn_fs_t *fs,
const svn_fs_x__id_t *id,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
return construct_txn_node_path(fs, id, PATH_EXT_CHILDREN, result_pool,
scratch_pool);
}
svn_error_t *
svn_fs_x__check_file_buffer_numeric(const char *buf,
apr_off_t offset,
const char *path,
const char *title,
apr_pool_t *scratch_pool)
{
const char *p;
for (p = buf + offset; *p; p++)
if (!svn_ctype_isdigit(*p))
return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
_("%s file '%s' contains unexpected non-digit '%c' within '%s'"),
title, svn_dirent_local_style(path, scratch_pool), *p, buf);
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_x__read_min_unpacked_rev(svn_revnum_t *min_unpacked_rev,
svn_fs_t *fs,
apr_pool_t *scratch_pool)
{
char buf[80];
apr_file_t *file;
apr_size_t len;
SVN_ERR(svn_io_file_open(&file,
svn_fs_x__path_min_unpacked_rev(fs, scratch_pool),
APR_READ | APR_BUFFERED,
APR_OS_DEFAULT,
scratch_pool));
len = sizeof(buf);
SVN_ERR(svn_io_read_length_line(file, buf, &len, scratch_pool));
SVN_ERR(svn_io_file_close(file, scratch_pool));
SVN_ERR(svn_revnum_parse(min_unpacked_rev, buf, NULL));
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_x__update_min_unpacked_rev(svn_fs_t *fs,
apr_pool_t *scratch_pool)
{
svn_fs_x__data_t *ffd = fs->fsap_data;
return svn_fs_x__read_min_unpacked_rev(&ffd->min_unpacked_rev, fs,
scratch_pool);
}
/* Write a file FILENAME in directory FS_PATH, containing a single line
* with the number REVNUM in ASCII decimal. Move the file into place
* atomically, overwriting any existing file.
*
* Similar to write_current(). */
svn_error_t *
svn_fs_x__write_min_unpacked_rev(svn_fs_t *fs,
svn_revnum_t revnum,
apr_pool_t *scratch_pool)
{
svn_fs_x__data_t *ffd = fs->fsap_data;
const char *final_path;
char buf[SVN_INT64_BUFFER_SIZE];
apr_size_t len = svn__i64toa(buf, revnum);
buf[len] = '\n';
final_path = svn_fs_x__path_min_unpacked_rev(fs, scratch_pool);
SVN_ERR(svn_io_write_atomic2(final_path, buf, len + 1,
final_path /* copy_perms */,
ffd->flush_to_disk, scratch_pool));
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_x__read_current(svn_revnum_t *rev,
svn_fs_t *fs,
apr_pool_t *scratch_pool)
{
const char *str;
svn_stringbuf_t *content;
SVN_ERR(svn_fs_x__read_content(&content,
svn_fs_x__path_current(fs, scratch_pool),
scratch_pool));
SVN_ERR(svn_revnum_parse(rev, content->data, &str));
if (*str != '\n')
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
_("Corrupt 'current' file"));
return SVN_NO_ERROR;
}
/* Atomically update the 'current' file to hold the specified REV.
Perform temporary allocations in SCRATCH_POOL. */
svn_error_t *
svn_fs_x__write_current(svn_fs_t *fs,
svn_revnum_t rev,
apr_pool_t *scratch_pool)
{
char *buf;
const char *tmp_name, *name;
apr_file_t *file;
/* Now we can just write out this line. */
buf = apr_psprintf(scratch_pool, "%ld\n", rev);
name = svn_fs_x__path_current(fs, scratch_pool);
tmp_name = svn_fs_x__path_next(fs, scratch_pool);
SVN_ERR(svn_io_file_open(&file, tmp_name,
APR_WRITE | APR_CREATE | APR_BUFFERED,
APR_OS_DEFAULT, scratch_pool));
SVN_ERR(svn_io_file_write_full(file, buf, strlen(buf), NULL,
scratch_pool));
SVN_ERR(svn_io_file_close(file, scratch_pool));
/* Copying permissions is a no-op on WIN32. */
SVN_ERR(svn_io_copy_perms(name, tmp_name, scratch_pool));
/* Move the file into place. */
SVN_ERR(svn_io_file_rename2(tmp_name, name, TRUE, scratch_pool));
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_x__try_stringbuf_from_file(svn_stringbuf_t **content,
svn_boolean_t *missing,
const char *path,
svn_boolean_t last_attempt,
apr_pool_t *result_pool)
{
svn_error_t *err = svn_stringbuf_from_file2(content, path, result_pool);
if (missing)
*missing = FALSE;
if (err)
{
*content = NULL;
if (APR_STATUS_IS_ENOENT(err->apr_err))
{
if (!last_attempt)
{
svn_error_clear(err);
if (missing)
*missing = TRUE;
return SVN_NO_ERROR;
}
}
#ifdef ESTALE
else if (APR_TO_OS_ERROR(err->apr_err) == ESTALE
|| APR_TO_OS_ERROR(err->apr_err) == EIO)
{
if (!last_attempt)
{
svn_error_clear(err);
return SVN_NO_ERROR;
}
}
#endif
}
return svn_error_trace(err);
}
/* Fetch the current offset of FILE into *OFFSET_P. */
svn_error_t *
svn_fs_x__read_content(svn_stringbuf_t **content,
const char *fname,
apr_pool_t *result_pool)
{
int i;
*content = NULL;
for (i = 0; !*content && (i < SVN_FS_X__RECOVERABLE_RETRY_COUNT); ++i)
SVN_ERR(svn_fs_x__try_stringbuf_from_file(content, NULL,
fname, i + 1 < SVN_FS_X__RECOVERABLE_RETRY_COUNT,
result_pool));
if (!*content)
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
_("Can't read '%s'"),
svn_dirent_local_style(fname, result_pool));
return SVN_NO_ERROR;
}
/* Reads a line from STREAM and converts it to a 64 bit integer to be
* returned in *RESULT. If we encounter eof, set *HIT_EOF and leave
* *RESULT unchanged. If HIT_EOF is NULL, EOF causes an "corrupt FS"
* error return.
* SCRATCH_POOL is used for temporary allocations.
*/
svn_error_t *
svn_fs_x__read_number_from_stream(apr_int64_t *result,
svn_boolean_t *hit_eof,
svn_stream_t *stream,
apr_pool_t *scratch_pool)
{
svn_stringbuf_t *sb;
svn_boolean_t eof;
svn_error_t *err;
SVN_ERR(svn_stream_readline(stream, &sb, "\n", &eof, scratch_pool));
if (hit_eof)
*hit_eof = eof;
else
if (eof)
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, _("Unexpected EOF"));
if (!eof)
{
err = svn_cstring_atoi64(result, sb->data);
if (err)
return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
_("Number '%s' invalid or too large"),
sb->data);
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_x__move_into_place(const char *old_filename,
const char *new_filename,
const char *perms_reference,
svn_fs_x__batch_fsync_t *batch,
apr_pool_t *scratch_pool)
{
/* Copying permissions is a no-op on WIN32. */
SVN_ERR(svn_io_copy_perms(perms_reference, old_filename, scratch_pool));
/* We use specific 'fsyncing move' Win32 API calls on Windows while the
* directory update fsync is POSIX-only. Moreover, there tend to be only
* a few moved files (1 or 2) per batch.
*
* Therefore, we use the platform-optimized "immediate" fsyncs on all
* non-POSIX platforms and the "scheduled" fsyncs on POSIX only.
*/
#if defined(SVN_ON_POSIX)
/* Move the file into place. */
SVN_ERR(svn_io_file_rename2(old_filename, new_filename, FALSE,
scratch_pool));
/* Schedule for synchronization. */
SVN_ERR(svn_fs_x__batch_fsync_new_path(batch, new_filename, scratch_pool));
#else
SVN_ERR(svn_io_file_rename2(old_filename, new_filename, TRUE,
scratch_pool));
#endif
return SVN_NO_ERROR;
}