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_hash.h"
30#include "svn_fs.h"
31#include "svn_dirent_uri.h"
32#include "svn_path.h"
33#include "svn_private_config.h"
34
35#include "private/svn_fs_util.h"
36#include "private/svn_fspath.h"
37#include "../libsvn_fs/fs-loader.h"
38
39/* Return TRUE, if PATH of PATH_LEN > 0 chars starts with a '/' and does
40 * not end with a '/' and does not contain duplicate '/'.
41 */
42static svn_boolean_t
43is_canonical_abspath(const char *path, size_t path_len)
44{
45  const char *end;
46
47  /* check for leading '/' */
48  if (path[0] != '/')
49    return FALSE;
50
51  /* check for trailing '/' */
52  if (path_len == 1)
53    return TRUE;
54  if (path[path_len - 1] == '/')
55    return FALSE;
56
57  /* check for "//" */
58  end = path + path_len - 1;
59  for (; path != end; ++path)
60    if ((path[0] == '/') && (path[1] == '/'))
61      return FALSE;
62
63  return TRUE;
64}
65
66svn_boolean_t
67svn_fs__is_canonical_abspath(const char *path)
68{
69  /* No PATH?  No problem. */
70  if (! path)
71    return TRUE;
72
73  /* Empty PATH?  That's just "/". */
74  if (! *path)
75    return FALSE;
76
77  /* detailed checks */
78  return is_canonical_abspath(path, strlen(path));
79}
80
81const char *
82svn_fs__canonicalize_abspath(const char *path, apr_pool_t *pool)
83{
84  char *newpath;
85  size_t path_len;
86  size_t path_i = 0, newpath_i = 0;
87  svn_boolean_t eating_slashes = FALSE;
88
89  /* No PATH?  No problem. */
90  if (! path)
91    return NULL;
92
93  /* Empty PATH?  That's just "/". */
94  if (! *path)
95    return "/";
96
97  /* Non-trivial cases.  Maybe, the path already is canonical after all? */
98  path_len = strlen(path);
99  if (is_canonical_abspath(path, path_len))
100    return apr_pstrmemdup(pool, path, path_len);
101
102  /* Now, the fun begins.  Alloc enough room to hold PATH with an
103     added leading '/'. */
104  newpath = apr_palloc(pool, path_len + 2);
105
106  /* No leading slash?  Fix that. */
107  if (*path != '/')
108    {
109      newpath[newpath_i++] = '/';
110    }
111
112  for (path_i = 0; path_i < path_len; path_i++)
113    {
114      if (path[path_i] == '/')
115        {
116          /* The current character is a '/'.  If we are eating up
117             extra '/' characters, skip this character.  Else, note
118             that we are now eating slashes. */
119          if (eating_slashes)
120            continue;
121          eating_slashes = TRUE;
122        }
123      else
124        {
125          /* The current character is NOT a '/'.  If we were eating
126             slashes, we need not do that any more. */
127          if (eating_slashes)
128            eating_slashes = FALSE;
129        }
130
131      /* Copy the current character into our new buffer. */
132      newpath[newpath_i++] = path[path_i];
133    }
134
135  /* Did we leave a '/' attached to the end of NEWPATH (other than in
136     the root directory case)? */
137  if ((newpath[newpath_i - 1] == '/') && (newpath_i > 1))
138    newpath[newpath_i - 1] = '\0';
139  else
140    newpath[newpath_i] = '\0';
141
142  return newpath;
143}
144
145svn_error_t *
146svn_fs__check_fs(svn_fs_t *fs,
147                 svn_boolean_t expect_open)
148{
149  if ((expect_open && fs->fsap_data)
150      || ((! expect_open) && (! fs->fsap_data)))
151    return SVN_NO_ERROR;
152  if (expect_open)
153    return svn_error_create(SVN_ERR_FS_NOT_OPEN, 0,
154                            _("Filesystem object has not been opened yet"));
155  else
156    return svn_error_create(SVN_ERR_FS_ALREADY_OPEN, 0,
157                            _("Filesystem object already open"));
158}
159
160char *
161svn_fs__next_entry_name(const char **next_p,
162                        const char *path,
163                        apr_pool_t *pool)
164{
165  const char *end;
166
167  /* Find the end of the current component.  */
168  end = strchr(path, '/');
169
170  if (! end)
171    {
172      /* The path contains only one component, with no trailing
173         slashes. */
174      *next_p = 0;
175      return apr_pstrdup(pool, path);
176    }
177  else
178    {
179      /* There's a slash after the first component.  Skip over an arbitrary
180         number of slashes to find the next one. */
181      const char *next = end;
182      while (*next == '/')
183        next++;
184      *next_p = next;
185      return apr_pstrndup(pool, path, end - path);
186    }
187}
188
189svn_fs_path_change2_t *
190svn_fs__path_change_create_internal(const svn_fs_id_t *node_rev_id,
191                                    svn_fs_path_change_kind_t change_kind,
192                                    apr_pool_t *pool)
193{
194  svn_fs_path_change2_t *change;
195
196  change = apr_pcalloc(pool, sizeof(*change));
197  change->node_rev_id = node_rev_id;
198  change->change_kind = change_kind;
199
200  return change;
201}
202
203svn_error_t *
204svn_fs__append_to_merged_froms(svn_mergeinfo_t *output,
205                               svn_mergeinfo_t input,
206                               const char *rel_path,
207                               apr_pool_t *pool)
208{
209  apr_hash_index_t *hi;
210
211  *output = apr_hash_make(pool);
212  for (hi = apr_hash_first(pool, input); hi; hi = apr_hash_next(hi))
213    {
214      const char *path = svn__apr_hash_index_key(hi);
215      svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi);
216
217      svn_hash_sets(*output,
218                    svn_fspath__join(path, rel_path, pool),
219                    svn_rangelist_dup(rangelist, pool));
220    }
221
222  return SVN_NO_ERROR;
223}
224