1/* fs-util.c : internal utility functions used by both FSFS and BDB back 2 * ends. 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24#include <string.h> 25 26#include <apr_pools.h> 27#include <apr_strings.h> 28 29#include "svn_private_config.h" 30#include "svn_hash.h" 31#include "svn_fs.h" 32#include "svn_dirent_uri.h" 33#include "svn_path.h" 34#include "svn_version.h" 35 36#include "private/svn_fs_util.h" 37#include "private/svn_fspath.h" 38#include "private/svn_subr_private.h" 39#include "../libsvn_fs/fs-loader.h" 40 41 42const svn_version_t * 43svn_fs_util__version(void) 44{ 45 SVN_VERSION_BODY; 46} 47 48 49/* Return TRUE, if PATH of PATH_LEN > 0 chars starts with a '/' and does 50 * not end with a '/' and does not contain duplicate '/'. 51 */ 52static svn_boolean_t 53is_canonical_abspath(const char *path, size_t path_len) 54{ 55 const char *end; 56 57 /* check for leading '/' */ 58 if (path[0] != '/') 59 return FALSE; 60 61 /* check for trailing '/' */ 62 if (path_len == 1) 63 return TRUE; 64 if (path[path_len - 1] == '/') 65 return FALSE; 66 67 /* check for "//" */ 68 end = path + path_len - 1; 69 for (; path != end; ++path) 70 if ((path[0] == '/') && (path[1] == '/')) 71 return FALSE; 72 73 return TRUE; 74} 75 76svn_boolean_t 77svn_fs__is_canonical_abspath(const char *path) 78{ 79 /* No PATH? No problem. */ 80 if (! path) 81 return TRUE; 82 83 /* Empty PATH? That's just "/". */ 84 if (! *path) 85 return FALSE; 86 87 /* detailed checks */ 88 return is_canonical_abspath(path, strlen(path)); 89} 90 91const char * 92svn_fs__canonicalize_abspath(const char *path, apr_pool_t *pool) 93{ 94 char *newpath; 95 size_t path_len; 96 size_t path_i = 0, newpath_i = 0; 97 svn_boolean_t eating_slashes = FALSE; 98 99 /* No PATH? No problem. */ 100 if (! path) 101 return NULL; 102 103 /* Empty PATH? That's just "/". */ 104 if (! *path) 105 return "/"; 106 107 /* Non-trivial cases. Maybe, the path already is canonical after all? */ 108 path_len = strlen(path); 109 if (is_canonical_abspath(path, path_len)) 110 return apr_pstrmemdup(pool, path, path_len); 111 112 /* Now, the fun begins. Alloc enough room to hold PATH with an 113 added leading '/'. */ 114 newpath = apr_palloc(pool, path_len + 2); 115 116 /* No leading slash? Fix that. */ 117 if (*path != '/') 118 { 119 newpath[newpath_i++] = '/'; 120 } 121 122 for (path_i = 0; path_i < path_len; path_i++) 123 { 124 if (path[path_i] == '/') 125 { 126 /* The current character is a '/'. If we are eating up 127 extra '/' characters, skip this character. Else, note 128 that we are now eating slashes. */ 129 if (eating_slashes) 130 continue; 131 eating_slashes = TRUE; 132 } 133 else 134 { 135 /* The current character is NOT a '/'. If we were eating 136 slashes, we need not do that any more. */ 137 if (eating_slashes) 138 eating_slashes = FALSE; 139 } 140 141 /* Copy the current character into our new buffer. */ 142 newpath[newpath_i++] = path[path_i]; 143 } 144 145 /* Did we leave a '/' attached to the end of NEWPATH (other than in 146 the root directory case)? */ 147 if ((newpath[newpath_i - 1] == '/') && (newpath_i > 1)) 148 newpath[newpath_i - 1] = '\0'; 149 else 150 newpath[newpath_i] = '\0'; 151 152 return newpath; 153} 154 155svn_error_t * 156svn_fs__check_fs(svn_fs_t *fs, 157 svn_boolean_t expect_open) 158{ 159 if ((expect_open && fs->fsap_data) 160 || ((! expect_open) && (! fs->fsap_data))) 161 return SVN_NO_ERROR; 162 if (expect_open) 163 return svn_error_create(SVN_ERR_FS_NOT_OPEN, 0, 164 _("Filesystem object has not been opened yet")); 165 else 166 return svn_error_create(SVN_ERR_FS_ALREADY_OPEN, 0, 167 _("Filesystem object already open")); 168} 169 170char * 171svn_fs__next_entry_name(const char **next_p, 172 const char *path, 173 apr_pool_t *pool) 174{ 175 const char *end; 176 177 /* Find the end of the current component. */ 178 end = strchr(path, '/'); 179 180 if (! end) 181 { 182 /* The path contains only one component, with no trailing 183 slashes. */ 184 *next_p = 0; 185 return apr_pstrdup(pool, path); 186 } 187 else 188 { 189 /* There's a slash after the first component. Skip over an arbitrary 190 number of slashes to find the next one. */ 191 const char *next = end; 192 while (*next == '/') 193 next++; 194 *next_p = next; 195 return apr_pstrndup(pool, path, end - path); 196 } 197} 198 199svn_fs_path_change2_t * 200svn_fs__path_change_create_internal(const svn_fs_id_t *node_rev_id, 201 svn_fs_path_change_kind_t change_kind, 202 apr_pool_t *pool) 203{ 204 svn_fs_path_change2_t *change; 205 206 change = apr_pcalloc(pool, sizeof(*change)); 207 change->node_rev_id = node_rev_id; 208 change->change_kind = change_kind; 209 change->mergeinfo_mod = svn_tristate_unknown; 210 change->copyfrom_rev = SVN_INVALID_REVNUM; 211 212 return change; 213} 214 215svn_error_t * 216svn_fs__append_to_merged_froms(svn_mergeinfo_t *output, 217 svn_mergeinfo_t input, 218 const char *rel_path, 219 apr_pool_t *pool) 220{ 221 apr_hash_index_t *hi; 222 223 *output = apr_hash_make(pool); 224 for (hi = apr_hash_first(pool, input); hi; hi = apr_hash_next(hi)) 225 { 226 const char *path = apr_hash_this_key(hi); 227 svn_rangelist_t *rangelist = apr_hash_this_val(hi); 228 229 svn_hash_sets(*output, 230 svn_fspath__join(path, rel_path, pool), 231 svn_rangelist_dup(rangelist, pool)); 232 } 233 234 return SVN_NO_ERROR; 235} 236 237/* Set the version info in *VERSION to COMPAT_MAJOR and COMPAT_MINOR, if 238 the current value refers to a newer version than that. 239 */ 240static void 241add_compatility(svn_version_t *version, 242 int compat_major, 243 int compat_minor) 244{ 245 if ( version->major > compat_major 246 || (version->major == compat_major && version->minor > compat_minor)) 247 { 248 version->major = compat_major; 249 version->minor = compat_minor; 250 } 251} 252 253svn_error_t * 254svn_fs__compatible_version(svn_version_t **compatible_version, 255 apr_hash_t *config, 256 apr_pool_t *pool) 257{ 258 svn_version_t *version; 259 const char *compatible; 260 261 /* set compatible version according to generic option. 262 Make sure, we are always compatible to the current SVN version 263 (or older). */ 264 compatible = svn_hash_gets(config, SVN_FS_CONFIG_COMPATIBLE_VERSION); 265 if (compatible) 266 { 267 SVN_ERR(svn_version__parse_version_string(&version, 268 compatible, pool)); 269 add_compatility(version, 270 svn_subr_version()->major, 271 svn_subr_version()->minor); 272 } 273 else 274 { 275 version = apr_pmemdup(pool, svn_subr_version(), sizeof(*version)); 276 } 277 278 /* specific options take precedence. 279 Let the lowest version compatibility requirement win */ 280 if (svn_hash_gets(config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE)) 281 add_compatility(version, 1, 3); 282 else if (svn_hash_gets(config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE)) 283 add_compatility(version, 1, 4); 284 else if (svn_hash_gets(config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE)) 285 add_compatility(version, 1, 5); 286 else if (svn_hash_gets(config, SVN_FS_CONFIG_PRE_1_8_COMPATIBLE)) 287 add_compatility(version, 1, 7); 288 289 /* we ignored the patch level and tag so far. 290 * Give them a defined value. */ 291 version->patch = 0; 292 version->tag = ""; 293 294 /* done here */ 295 *compatible_version = version; 296 return SVN_NO_ERROR; 297} 298 299svn_boolean_t 300svn_fs__prop_lists_equal(apr_hash_t *a, 301 apr_hash_t *b, 302 apr_pool_t *pool) 303{ 304 apr_hash_index_t *hi; 305 306 /* Quick checks and special cases. */ 307 if (a == b) 308 return TRUE; 309 310 if (a == NULL) 311 return apr_hash_count(b) == 0; 312 if (b == NULL) 313 return apr_hash_count(a) == 0; 314 315 if (apr_hash_count(a) != apr_hash_count(b)) 316 return FALSE; 317 318 /* Compare prop by prop. */ 319 for (hi = apr_hash_first(pool, a); hi; hi = apr_hash_next(hi)) 320 { 321 const char *key; 322 apr_ssize_t klen; 323 svn_string_t *val_a, *val_b; 324 325 apr_hash_this(hi, (const void **)&key, &klen, (void **)&val_a); 326 val_b = apr_hash_get(b, key, klen); 327 328 if (!val_b || !svn_string_compare(val_a, val_b)) 329 return FALSE; 330 } 331 332 /* No difference found. */ 333 return TRUE; 334} 335