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