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
42 const svn_version_t *
svn_fs_util__version(void)43 svn_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 */
52 static svn_boolean_t
is_canonical_abspath(const char * path,size_t path_len)53 is_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
76 svn_boolean_t
svn_fs__is_canonical_abspath(const char * path)77 svn_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
91 const char *
svn_fs__canonicalize_abspath(const char * path,apr_pool_t * pool)92 svn_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
155 svn_error_t *
svn_fs__check_fs(svn_fs_t * fs,svn_boolean_t expect_open)156 svn_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
170 char *
svn_fs__next_entry_name(const char ** next_p,const char * path,apr_pool_t * pool)171 svn_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
199 svn_fs_path_change2_t *
svn_fs__path_change_create_internal(const svn_fs_id_t * node_rev_id,svn_fs_path_change_kind_t change_kind,apr_pool_t * pool)200 svn_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
215 svn_fs_path_change3_t *
svn_fs__path_change_create_internal2(svn_fs_path_change_kind_t change_kind,apr_pool_t * result_pool)216 svn_fs__path_change_create_internal2(svn_fs_path_change_kind_t change_kind,
217 apr_pool_t *result_pool)
218 {
219 svn_fs_path_change3_t *change;
220
221 change = apr_pcalloc(result_pool, sizeof(*change));
222 change->path.data = "";
223 change->change_kind = change_kind;
224 change->mergeinfo_mod = svn_tristate_unknown;
225 change->copyfrom_rev = SVN_INVALID_REVNUM;
226
227 return change;
228 }
229
230 svn_error_t *
svn_fs__append_to_merged_froms(svn_mergeinfo_t * output,svn_mergeinfo_t input,const char * rel_path,apr_pool_t * pool)231 svn_fs__append_to_merged_froms(svn_mergeinfo_t *output,
232 svn_mergeinfo_t input,
233 const char *rel_path,
234 apr_pool_t *pool)
235 {
236 apr_hash_index_t *hi;
237
238 *output = apr_hash_make(pool);
239 for (hi = apr_hash_first(pool, input); hi; hi = apr_hash_next(hi))
240 {
241 const char *path = apr_hash_this_key(hi);
242 svn_rangelist_t *rangelist = apr_hash_this_val(hi);
243
244 svn_hash_sets(*output,
245 svn_fspath__join(path, rel_path, pool),
246 svn_rangelist_dup(rangelist, pool));
247 }
248
249 return SVN_NO_ERROR;
250 }
251
252 /* Set the version info in *VERSION to COMPAT_MAJOR and COMPAT_MINOR, if
253 the current value refers to a newer version than that.
254 */
255 static void
add_compatility(svn_version_t * version,int compat_major,int compat_minor)256 add_compatility(svn_version_t *version,
257 int compat_major,
258 int compat_minor)
259 {
260 if ( version->major > compat_major
261 || (version->major == compat_major && version->minor > compat_minor))
262 {
263 version->major = compat_major;
264 version->minor = compat_minor;
265 }
266 }
267
268 svn_error_t *
svn_fs__compatible_version(svn_version_t ** compatible_version,apr_hash_t * config,apr_pool_t * pool)269 svn_fs__compatible_version(svn_version_t **compatible_version,
270 apr_hash_t *config,
271 apr_pool_t *pool)
272 {
273 svn_version_t *version;
274 const char *compatible;
275
276 /* set compatible version according to generic option.
277 Make sure, we are always compatible to the current SVN version
278 (or older). */
279 compatible = svn_hash_gets(config, SVN_FS_CONFIG_COMPATIBLE_VERSION);
280 if (compatible)
281 {
282 SVN_ERR(svn_version__parse_version_string(&version,
283 compatible, pool));
284 add_compatility(version,
285 svn_subr_version()->major,
286 svn_subr_version()->minor);
287 }
288 else
289 {
290 version = apr_pmemdup(pool, svn_subr_version(), sizeof(*version));
291 }
292
293 /* specific options take precedence.
294 Let the lowest version compatibility requirement win */
295 if (svn_hash_gets(config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE))
296 add_compatility(version, 1, 3);
297 else if (svn_hash_gets(config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE))
298 add_compatility(version, 1, 4);
299 else if (svn_hash_gets(config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE))
300 add_compatility(version, 1, 5);
301 else if (svn_hash_gets(config, SVN_FS_CONFIG_PRE_1_8_COMPATIBLE))
302 add_compatility(version, 1, 7);
303
304 /* we ignored the patch level and tag so far.
305 * Give them a defined value. */
306 version->patch = 0;
307 version->tag = "";
308
309 /* done here */
310 *compatible_version = version;
311 return SVN_NO_ERROR;
312 }
313
314 svn_boolean_t
svn_fs__prop_lists_equal(apr_hash_t * a,apr_hash_t * b,apr_pool_t * pool)315 svn_fs__prop_lists_equal(apr_hash_t *a,
316 apr_hash_t *b,
317 apr_pool_t *pool)
318 {
319 apr_hash_index_t *hi;
320
321 /* Quick checks and special cases. */
322 if (a == b)
323 return TRUE;
324
325 if (a == NULL)
326 return apr_hash_count(b) == 0;
327 if (b == NULL)
328 return apr_hash_count(a) == 0;
329
330 if (apr_hash_count(a) != apr_hash_count(b))
331 return FALSE;
332
333 /* Compare prop by prop. */
334 for (hi = apr_hash_first(pool, a); hi; hi = apr_hash_next(hi))
335 {
336 const char *key;
337 apr_ssize_t klen;
338 svn_string_t *val_a, *val_b;
339
340 apr_hash_this(hi, (const void **)&key, &klen, (void **)&val_a);
341 val_b = apr_hash_get(b, key, klen);
342
343 if (!val_b || !svn_string_compare(val_a, val_b))
344 return FALSE;
345 }
346
347 /* No difference found. */
348 return TRUE;
349 }
350