diff options
Diffstat (limited to 'subversion/libsvn_fs_fs/util.c')
| -rw-r--r-- | subversion/libsvn_fs_fs/util.c | 694 | 
1 files changed, 694 insertions, 0 deletions
diff --git a/subversion/libsvn_fs_fs/util.c b/subversion/libsvn_fs_fs/util.c new file mode 100644 index 000000000000..faa1e3d319b2 --- /dev/null +++ b/subversion/libsvn_fs_fs/util.c @@ -0,0 +1,694 @@ +/* util.c --- utility functions for FSFS 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_fs.h" +#include "pack.h" +#include "util.h" + +#include "../libsvn_fs/fs-loader.h" + +#include "svn_private_config.h" + +svn_boolean_t +svn_fs_fs__is_packed_rev(svn_fs_t *fs, +                         svn_revnum_t rev) +{ +  fs_fs_data_t *ffd = fs->fsap_data; + +  return (rev < ffd->min_unpacked_rev); +} + +svn_boolean_t +svn_fs_fs__is_packed_revprop(svn_fs_t *fs, +                             svn_revnum_t rev) +{ +  fs_fs_data_t *ffd = fs->fsap_data; + +  /* rev 0 will not be packed */ +  return (rev < ffd->min_unpacked_rev) +      && (rev != 0) +      && (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT); +} + +svn_revnum_t +svn_fs_fs__packed_base_rev(svn_fs_t *fs, +                           svn_revnum_t revision) +{ +  fs_fs_data_t *ffd = fs->fsap_data; +  return (revision < ffd->min_unpacked_rev) +       ? (revision - (revision % ffd->max_files_per_dir)) +       : revision; +} + +const char * +svn_fs_fs__path_txn_current(svn_fs_t *fs, +                            apr_pool_t *pool) +{ +  return svn_dirent_join(fs->path, PATH_TXN_CURRENT, pool); +} + +const char * +svn_fs_fs__path_txn_current_lock(svn_fs_t *fs, +                                 apr_pool_t *pool) +{ +  return svn_dirent_join(fs->path, PATH_TXN_CURRENT_LOCK, pool); +} + +const char * +svn_fs_fs__path_lock(svn_fs_t *fs, +                     apr_pool_t *pool) +{ +  return svn_dirent_join(fs->path, PATH_LOCK_FILE, pool); +} + +const char * +svn_fs_fs__path_pack_lock(svn_fs_t *fs, +                          apr_pool_t *pool) +{ +  return svn_dirent_join(fs->path, PATH_PACK_LOCK_FILE, pool); +} + +const char * +svn_fs_fs__path_revprop_generation(svn_fs_t *fs, +                                   apr_pool_t *pool) +{ +  return svn_dirent_join(fs->path, PATH_REVPROP_GENERATION, pool); +} + +const char * +svn_fs_fs__path_rev_packed(svn_fs_t *fs, +                           svn_revnum_t rev, +                           const char *kind, +                           apr_pool_t *pool) +{ +  fs_fs_data_t *ffd = fs->fsap_data; + +  assert(ffd->max_files_per_dir); +  assert(svn_fs_fs__is_packed_rev(fs, rev)); + +  return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR, +                              apr_psprintf(pool, +                                           "%ld" PATH_EXT_PACKED_SHARD, +                                           rev / ffd->max_files_per_dir), +                              kind, SVN_VA_NULL); +} + +const char * +svn_fs_fs__path_rev_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool) +{ +  fs_fs_data_t *ffd = fs->fsap_data; + +  assert(ffd->max_files_per_dir); +  return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR, +                              apr_psprintf(pool, "%ld", +                                                 rev / ffd->max_files_per_dir), +                              SVN_VA_NULL); +} + +const char * +svn_fs_fs__path_rev(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool) +{ +  fs_fs_data_t *ffd = fs->fsap_data; + +  assert(! svn_fs_fs__is_packed_rev(fs, rev)); + +  if (ffd->max_files_per_dir) +    { +      return svn_dirent_join(svn_fs_fs__path_rev_shard(fs, rev, pool), +                             apr_psprintf(pool, "%ld", rev), +                             pool); +    } + +  return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR, +                              apr_psprintf(pool, "%ld", rev), SVN_VA_NULL); +} + +/* Set *PATH to the path of REV in FS with PACKED selecting whether the +   (potential) pack file or single revision file name is returned. +   Allocate *PATH in POOL. +*/ +static const char * +path_rev_absolute_internal(svn_fs_t *fs, +                           svn_revnum_t rev, +                           svn_boolean_t packed, +                           apr_pool_t *pool) +{ +  return packed +       ? svn_fs_fs__path_rev_packed(fs, rev, PATH_PACKED, pool) +       : svn_fs_fs__path_rev(fs, rev, pool); +} + +const char * +svn_fs_fs__path_rev_absolute(svn_fs_t *fs, +                             svn_revnum_t rev, +                             apr_pool_t *pool) +{ +  fs_fs_data_t *ffd = fs->fsap_data; +  svn_boolean_t is_packed = ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT +                         && svn_fs_fs__is_packed_rev(fs, rev); + +  return path_rev_absolute_internal(fs, rev, is_packed, pool); +} + +const char * +svn_fs_fs__path_revprops_shard(svn_fs_t *fs, +                               svn_revnum_t rev, +                               apr_pool_t *pool) +{ +  fs_fs_data_t *ffd = fs->fsap_data; + +  assert(ffd->max_files_per_dir); +  return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR, +                              apr_psprintf(pool, "%ld", +                                           rev / ffd->max_files_per_dir), +                              SVN_VA_NULL); +} + +const char * +svn_fs_fs__path_revprops_pack_shard(svn_fs_t *fs, +                                    svn_revnum_t rev, +                                    apr_pool_t *pool) +{ +  fs_fs_data_t *ffd = fs->fsap_data; + +  assert(ffd->max_files_per_dir); +  return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR, +                              apr_psprintf(pool, "%ld" PATH_EXT_PACKED_SHARD, +                                           rev / ffd->max_files_per_dir), +                              SVN_VA_NULL); +} + +const char * +svn_fs_fs__path_revprops(svn_fs_t *fs, +                         svn_revnum_t rev, +                         apr_pool_t *pool) +{ +  fs_fs_data_t *ffd = fs->fsap_data; + +  if (ffd->max_files_per_dir) +    { +      return svn_dirent_join(svn_fs_fs__path_revprops_shard(fs, rev, pool), +                             apr_psprintf(pool, "%ld", rev), +                             pool); +    } + +  return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR, +                              apr_psprintf(pool, "%ld", rev), SVN_VA_NULL); +} + +/* Return TO_ADD appended to the C string representation of TXN_ID. + * Allocate the result in POOL. + */ +static const char * +combine_txn_id_string(const svn_fs_fs__id_part_t *txn_id, +                      const char *to_add, +                      apr_pool_t *pool) +{ +  return apr_pstrcat(pool, svn_fs_fs__id_txn_unparse(txn_id, pool), +                     to_add, SVN_VA_NULL); +} + +const char * +svn_fs_fs__path_txns_dir(svn_fs_t *fs, +                         apr_pool_t *pool) +{ +  return svn_dirent_join(fs->path, PATH_TXNS_DIR, pool); +} + +const char * +svn_fs_fs__path_txn_dir(svn_fs_t *fs, +                        const svn_fs_fs__id_part_t *txn_id, +                        apr_pool_t *pool) +{ +  SVN_ERR_ASSERT_NO_RETURN(txn_id != NULL); +  return svn_dirent_join(svn_fs_fs__path_txns_dir(fs, pool), +                         combine_txn_id_string(txn_id, PATH_EXT_TXN, pool), +                         pool); +} + +const char* +svn_fs_fs__path_l2p_proto_index(svn_fs_t *fs, +                                const svn_fs_fs__id_part_t *txn_id, +                                apr_pool_t *pool) +{ +  return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool), +                         PATH_INDEX PATH_EXT_L2P_INDEX, pool); +} + +const char* +svn_fs_fs__path_p2l_proto_index(svn_fs_t *fs, +                                const svn_fs_fs__id_part_t *txn_id, +                                apr_pool_t *pool) +{ +  return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool), +                         PATH_INDEX PATH_EXT_P2L_INDEX, pool); +} + +const char * +svn_fs_fs__path_txn_item_index(svn_fs_t *fs, +                               const svn_fs_fs__id_part_t *txn_id, +                               apr_pool_t *pool) +{ +  return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool), +                         PATH_TXN_ITEM_INDEX, pool); +} + +const char * +svn_fs_fs__path_txn_proto_revs(svn_fs_t *fs, +                               apr_pool_t *pool) +{ +  return svn_dirent_join(fs->path, PATH_TXN_PROTOS_DIR, pool); +} + +const char * +svn_fs_fs__path_txn_proto_rev(svn_fs_t *fs, +                              const svn_fs_fs__id_part_t *txn_id, +                              apr_pool_t *pool) +{ +  fs_fs_data_t *ffd = fs->fsap_data; +  if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT) +    return svn_dirent_join(svn_fs_fs__path_txn_proto_revs(fs, pool), +                           combine_txn_id_string(txn_id, PATH_EXT_REV, pool), +                           pool); +  else +    return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool), +                           PATH_REV, pool); +} + + +const char * +svn_fs_fs__path_txn_proto_rev_lock(svn_fs_t *fs, +                                   const svn_fs_fs__id_part_t *txn_id, +                                   apr_pool_t *pool) +{ +  fs_fs_data_t *ffd = fs->fsap_data; +  if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT) +    return svn_dirent_join(svn_fs_fs__path_txn_proto_revs(fs, pool), +                           combine_txn_id_string(txn_id, PATH_EXT_REV_LOCK, +                                                 pool), +                           pool); +  else +    return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool), +                           PATH_REV_LOCK, pool); +} + +const char * +svn_fs_fs__path_txn_node_rev(svn_fs_t *fs, +                             const svn_fs_id_t *id, +                             apr_pool_t *pool) +{ +  char *filename = (char *)svn_fs_fs__id_unparse(id, pool)->data; +  *strrchr(filename, '.') = '\0'; + +  return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, svn_fs_fs__id_txn_id(id), +                                                 pool), +                         apr_psprintf(pool, PATH_PREFIX_NODE "%s", +                                      filename), +                         pool); +} + +const char * +svn_fs_fs__path_txn_node_props(svn_fs_t *fs, +                               const svn_fs_id_t *id, +                               apr_pool_t *pool) +{ +  return apr_pstrcat(pool, svn_fs_fs__path_txn_node_rev(fs, id, pool), +                     PATH_EXT_PROPS, SVN_VA_NULL); +} + +const char * +svn_fs_fs__path_txn_node_children(svn_fs_t *fs, +                                  const svn_fs_id_t *id, +                                  apr_pool_t *pool) +{ +  return apr_pstrcat(pool, svn_fs_fs__path_txn_node_rev(fs, id, pool), +                     PATH_EXT_CHILDREN, SVN_VA_NULL); +} + +const char * +svn_fs_fs__path_node_origin(svn_fs_t *fs, +                            const svn_fs_fs__id_part_t *node_id, +                            apr_pool_t *pool) +{ +  char buffer[SVN_INT64_BUFFER_SIZE]; +  apr_size_t len = svn__ui64tobase36(buffer, node_id->number); + +  if (len > 1) +    buffer[len - 1] = '\0'; + +  return svn_dirent_join_many(pool, fs->path, PATH_NODE_ORIGINS_DIR, +                              buffer, SVN_VA_NULL); +} + +const char * +svn_fs_fs__path_min_unpacked_rev(svn_fs_t *fs, +                                 apr_pool_t *pool) +{ +  return svn_dirent_join(fs->path, PATH_MIN_UNPACKED_REV, pool); +} + +svn_error_t * +svn_fs_fs__check_file_buffer_numeric(const char *buf, +                                     apr_off_t offset, +                                     const char *path, +                                     const char *title, +                                     apr_pool_t *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, pool), *p, buf); + +  return SVN_NO_ERROR; +} + +svn_error_t * +svn_fs_fs__read_min_unpacked_rev(svn_revnum_t *min_unpacked_rev, +                                 svn_fs_t *fs, +                                 apr_pool_t *pool) +{ +  char buf[80]; +  apr_file_t *file; +  apr_size_t len; + +  SVN_ERR(svn_io_file_open(&file, +                           svn_fs_fs__path_min_unpacked_rev(fs, pool), +                           APR_READ | APR_BUFFERED, +                           APR_OS_DEFAULT, +                           pool)); +  len = sizeof(buf); +  SVN_ERR(svn_io_read_length_line(file, buf, &len, pool)); +  SVN_ERR(svn_io_file_close(file, pool)); + +  SVN_ERR(svn_revnum_parse(min_unpacked_rev, buf, NULL)); +  return SVN_NO_ERROR; +} + +svn_error_t * +svn_fs_fs__update_min_unpacked_rev(svn_fs_t *fs, +                                   apr_pool_t *pool) +{ +  fs_fs_data_t *ffd = fs->fsap_data; + +  SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT); + +  return svn_fs_fs__read_min_unpacked_rev(&ffd->min_unpacked_rev, fs, pool); +} + +svn_error_t * +svn_fs_fs__write_min_unpacked_rev(svn_fs_t *fs, +                                  svn_revnum_t revnum, +                                  apr_pool_t *scratch_pool) +{ +  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_fs__path_min_unpacked_rev(fs, scratch_pool); + +  SVN_ERR(svn_io_write_atomic(final_path, buf, len + 1, +                              final_path /* copy_perms */, scratch_pool)); + +  return SVN_NO_ERROR; +} + +svn_error_t * +svn_fs_fs__read_current(svn_revnum_t *rev, +                        apr_uint64_t *next_node_id, +                        apr_uint64_t *next_copy_id, +                        svn_fs_t *fs, +                        apr_pool_t *pool) +{ +  fs_fs_data_t *ffd = fs->fsap_data; +  svn_stringbuf_t *content; + +  SVN_ERR(svn_fs_fs__read_content(&content, +                                  svn_fs_fs__path_current(fs, pool), +                                  pool)); + +  if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT) +    { +      /* When format 1 and 2 filesystems are upgraded, the 'current' file is +         left intact.  As a consequence, there is a window when a filesystem +         has a new format, but this file still contains the IDs left from an +         old format, i.e. looks like "359 j5 v\n".  Do not be too strict here +         and only expect a parseable revision number. */ +      SVN_ERR(svn_revnum_parse(rev, content->data, NULL)); + +      *next_node_id = 0; +      *next_copy_id = 0; +    } +  else +    { +      const char *str; + +      SVN_ERR(svn_revnum_parse(rev, content->data, &str)); +      if (*str != ' ') +        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, +                                _("Corrupt 'current' file")); + +      *next_node_id = svn__base36toui64(&str, str + 1); +      if (*str != ' ') +        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, +                                _("Corrupt 'current' file")); + +      *next_copy_id = svn__base36toui64(&str, str + 1); +      if (*str != '\n') +        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, +                                _("Corrupt 'current' file")); +    } + +  return SVN_NO_ERROR; +} + +svn_error_t * +svn_fs_fs__write_current(svn_fs_t *fs, +                         svn_revnum_t rev, +                         apr_uint64_t next_node_id, +                         apr_uint64_t next_copy_id, +                         apr_pool_t *pool) +{ +  char *buf; +  const char *name; +  fs_fs_data_t *ffd = fs->fsap_data; + +  /* Now we can just write out this line. */ +  if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT) +    { +      buf = apr_psprintf(pool, "%ld\n", rev); +    } +  else +    { +      char node_id_str[SVN_INT64_BUFFER_SIZE]; +      char copy_id_str[SVN_INT64_BUFFER_SIZE]; +      svn__ui64tobase36(node_id_str, next_node_id); +      svn__ui64tobase36(copy_id_str, next_copy_id); + +      buf = apr_psprintf(pool, "%ld %s %s\n", rev, node_id_str, copy_id_str); +    } + +  name = svn_fs_fs__path_current(fs, pool); +  SVN_ERR(svn_io_write_atomic(name, buf, strlen(buf), +                              name /* copy_perms_path */, pool)); + +  return SVN_NO_ERROR; +} + +svn_error_t * +svn_fs_fs__try_stringbuf_from_file(svn_stringbuf_t **content, +                                   svn_boolean_t *missing, +                                   const char *path, +                                   svn_boolean_t last_attempt, +                                   apr_pool_t *pool) +{ +  svn_error_t *err = svn_stringbuf_from_file2(content, path, 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); +} + +svn_error_t * +svn_fs_fs__get_file_offset(apr_off_t *offset_p, +                           apr_file_t *file, +                           apr_pool_t *pool) +{ +  apr_off_t offset; + +  /* Note that, for buffered files, one (possibly surprising) side-effect +     of this call is to flush any unwritten data to disk. */ +  offset = 0; +  SVN_ERR(svn_io_file_seek(file, APR_CUR, &offset, pool)); +  *offset_p = offset; + +  return SVN_NO_ERROR; +} + +svn_error_t * +svn_fs_fs__read_content(svn_stringbuf_t **content, +                        const char *fname, +                        apr_pool_t *pool) +{ +  int i; +  *content = NULL; + +  for (i = 0; !*content && (i < SVN_FS_FS__RECOVERABLE_RETRY_COUNT); ++i) +    SVN_ERR(svn_fs_fs__try_stringbuf_from_file(content, NULL, +                        fname, i + 1 < SVN_FS_FS__RECOVERABLE_RETRY_COUNT, +                        pool)); + +  if (!*content) +    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, +                             _("Can't read '%s'"), +                             svn_dirent_local_style(fname, pool)); + +  return SVN_NO_ERROR; +} + +svn_error_t * +svn_fs_fs__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_fs__move_into_place(const char *old_filename, +                           const char *new_filename, +                           const char *perms_reference, +                           apr_pool_t *pool) +{ +  svn_error_t *err; + +  SVN_ERR(svn_io_copy_perms(perms_reference, old_filename, pool)); + +  /* Move the file into place. */ +  err = svn_io_file_rename(old_filename, new_filename, pool); +  if (err && APR_STATUS_IS_EXDEV(err->apr_err)) +    { +      apr_file_t *file; + +      /* Can't rename across devices; fall back to copying. */ +      svn_error_clear(err); +      err = SVN_NO_ERROR; +      SVN_ERR(svn_io_copy_file(old_filename, new_filename, TRUE, pool)); + +      /* Flush the target of the copy to disk. */ +      SVN_ERR(svn_io_file_open(&file, new_filename, APR_READ, +                               APR_OS_DEFAULT, pool)); +      /* ### BH: Does this really guarantee a flush of the data written +         ### via a completely different handle on all operating systems? +         ### +         ### Maybe we should perform the copy ourselves instead of making +         ### apr do that and flush the real handle? */ +      SVN_ERR(svn_io_file_flush_to_disk(file, pool)); +      SVN_ERR(svn_io_file_close(file, pool)); +    } +  if (err) +    return svn_error_trace(err); + +#ifdef __linux__ +  { +    /* Linux has the unusual feature that fsync() on a file is not +       enough to ensure that a file's directory entries have been +       flushed to disk; you have to fsync the directory as well. +       On other operating systems, we'd only be asking for trouble +       by trying to open and fsync a directory. */ +    const char *dirname; +    apr_file_t *file; + +    dirname = svn_dirent_dirname(new_filename, pool); +    SVN_ERR(svn_io_file_open(&file, dirname, APR_READ, APR_OS_DEFAULT, +                             pool)); +    SVN_ERR(svn_io_file_flush_to_disk(file, pool)); +    SVN_ERR(svn_io_file_close(file, pool)); +  } +#endif + +  return SVN_NO_ERROR; +} + +svn_boolean_t +svn_fs_fs__use_log_addressing(svn_fs_t *fs) +{ +  fs_fs_data_t *ffd = fs->fsap_data; +  return ffd->use_log_addressing; +}  | 
