Allow runtime to be built with C++ on AIX (#17672)
[mono-project.git] / mono / utils / mono-path.c
blob2f4790666a42805476c26f871c38560e56197a82
1 /**
2 * \file
3 * Routines for handling path names.
4 *
5 * Authors:
6 * Gonzalo Paniagua Javier (gonzalo@novell.com)
7 * Miguel de Icaza (miguel@novell.com)
9 * (C) 2006 Novell, Inc. http://www.novell.com
12 #include <config.h>
13 #include <glib.h>
14 #include <errno.h>
15 #include <string.h>
16 #include <stdlib.h>
17 #ifdef HAVE_UNISTD_H
18 #include <unistd.h>
19 #endif
20 /* This is only needed for the mono_path_canonicalize code, MAXSYMLINKS, could be moved */
21 #ifdef HAVE_SYS_PARAM_H
22 #include <sys/param.h>
23 #endif
25 #include "mono-path.h"
27 /* Embedded systems lack MAXSYMLINKS */
28 #ifndef MAXSYMLINKS
29 #define MAXSYMLINKS 3
30 #endif
32 /* Resolves '..' and '.' references in a path. If the path provided is relative,
33 * it will be relative to the current directory */
35 /* For Native Client, the above is not true. Since there is no getcwd we fill */
36 /* in the file being passed in relative to '.' and don't resolve it */
38 /* There are a couple of tests for this method in mono/test/mono-path.cs */
39 gchar *
40 mono_path_canonicalize (const char *path)
42 gchar *abspath, *pos, *lastpos, *dest;
43 int backc;
45 if (g_path_is_absolute (path)) {
46 abspath = g_strdup (path);
47 } else {
48 gchar *tmpdir = g_get_current_dir ();
49 abspath = g_build_filename (tmpdir, path, (const char*)NULL);
50 g_free (tmpdir);
53 #ifdef HOST_WIN32
54 g_strdelimit (abspath, '/', '\\');
55 #endif
56 abspath = g_strreverse (abspath);
58 backc = 0;
59 dest = lastpos = abspath;
60 pos = strchr (lastpos, G_DIR_SEPARATOR);
62 while (pos != NULL) {
63 int len = pos - lastpos;
64 if (len == 1 && lastpos [0] == '.') {
65 // nop
66 } else if (len == 2 && lastpos [0] == '.' && lastpos [1] == '.') {
67 backc++;
68 } else if (len > 0) {
69 if (backc > 0) {
70 backc--;
71 } else {
72 if (dest != lastpos)
73 /* The two strings can overlap */
74 memmove (dest, lastpos, len + 1);
75 dest += len + 1;
78 lastpos = pos + 1;
79 pos = strchr (lastpos, G_DIR_SEPARATOR);
82 #ifdef HOST_WIN32
83 /* Avoid removing the first '\' for UNC paths. We must make sure that it's indeed an UNC path
84 by checking if the \\ pair happens exactly at the end of the string.
86 if (*(lastpos-1) == G_DIR_SEPARATOR && *(lastpos-2) == G_DIR_SEPARATOR && *lastpos == 0)
87 lastpos = lastpos-1;
88 #endif
90 if (dest != lastpos) strcpy (dest, lastpos);
92 g_strreverse (abspath);
94 /* We strip away all trailing dir separators. This is not correct for the root directory,
95 * since we'll return an empty string, so re-append a dir separator if there is none in the
96 * result */
97 if (strchr (abspath, G_DIR_SEPARATOR) == NULL) {
98 int len = strlen (abspath);
99 abspath = (gchar *) g_realloc (abspath, len + 2);
100 abspath [len] = G_DIR_SEPARATOR;
101 abspath [len+1] = 0;
104 return abspath;
108 * This ensures that the path that we store points to the final file
109 * not a path to a symlink.
111 #if !defined(HOST_NO_SYMLINKS)
112 static gchar *
113 resolve_symlink (const char *path)
115 char *p, *concat, *dir;
116 char buffer [PATH_MAX+1];
117 int n, iterations = 0;
119 p = g_strdup (path);
120 do {
121 iterations++;
122 n = readlink (p, buffer, sizeof (buffer)-1);
123 if (n < 0){
124 char *copy = p;
125 p = mono_path_canonicalize (copy);
126 g_free (copy);
127 return p;
130 buffer [n] = 0;
131 if (!g_path_is_absolute (buffer)) {
132 dir = g_path_get_dirname (p);
133 concat = g_build_filename (dir, buffer, (const char*)NULL);
134 g_free (dir);
135 } else {
136 concat = g_strdup (buffer);
138 g_free (p);
139 p = mono_path_canonicalize (concat);
140 g_free (concat);
141 } while (iterations < MAXSYMLINKS);
143 return p;
145 #endif
147 gchar *
148 mono_path_resolve_symlinks (const char *path)
150 #if defined(HOST_NO_SYMLINKS)
151 return mono_path_canonicalize (path);
152 #else
153 gchar **split = g_strsplit (path, G_DIR_SEPARATOR_S, -1);
154 gchar *p = g_strdup ("");
155 int i;
157 for (i = 0; split [i] != NULL; i++) {
158 gchar *tmp = NULL;
160 // resolve_symlink of "" goes into canonicalize which resolves to cwd
161 if (strcmp (split [i], "") != 0) {
162 tmp = g_strdup_printf ("%s%s", p, split [i]);
163 g_free (p);
164 p = resolve_symlink (tmp);
165 g_free (tmp);
168 if (split [i+1] != NULL) {
169 tmp = g_strdup_printf ("%s%s", p, G_DIR_SEPARATOR_S);
170 g_free (p);
171 p = tmp;
175 g_strfreev (split);
176 return p;
177 #endif
180 static gboolean
181 mono_path_char_is_separator (char ch)
183 #ifdef HOST_WIN32
184 return ch == '/' || ch == '\\';
185 #else
186 return ch == '/';
187 #endif
190 static gboolean
191 mono_path_contains_separator (const char *path, size_t length)
193 for (size_t i = 0; i < length; ++i) {
194 if (mono_path_char_is_separator (path [i]))
195 return TRUE;
197 return FALSE;
200 static void
201 mono_path_remove_trailing_path_separators (const char *path, size_t *length)
203 size_t i = *length;
204 while (i > 0 && mono_path_char_is_separator (path [i - 1]))
205 i -= 1;
206 *length = i;
209 #ifdef HOST_WIN32
211 static gboolean
212 mono_path_char_is_lowercase (char ch)
214 return ch >= 'a' && ch <= 'z';
217 // Version-specific unichar2 upcase tables are stored per-volume at NTFS format-time.
218 // This is just a subset.
219 static char
220 mono_path_char_upcase (char a)
222 return mono_path_char_is_lowercase (a) ? (char)(a - 'a' + 'A') : a;
225 static gboolean
226 mono_path_char_equal (char a, char b)
228 return a == b
229 || mono_path_char_upcase (a) == mono_path_char_upcase (b)
230 || (mono_path_char_is_separator (a) && mono_path_char_is_separator (b));
233 #endif
235 static gboolean
236 mono_path_equal (const char *a, const char *b, size_t length)
238 #ifdef HOST_WIN32
239 size_t i = 0;
240 for (i = 0; i < length && mono_path_char_equal (a [i], b [i]); ++i) {
241 // nothing
243 return i == length;
244 #else
245 return memcmp (a, b, length) == 0;
246 #endif
249 static size_t
250 mono_path_path_separator_length (const char *a, size_t length)
252 size_t i = 0;
253 while (i < length && mono_path_char_is_separator (a [i]))
254 ++i;
255 return i;
259 * mono_path_filename_in_basedir:
261 * Return \c TRUE if \p filename is "immediately" in \p basedir
263 * Both paths should be absolute and be mostly normalized.
264 * If the file is in a subdirectory of \p basedir, returns \c FALSE.
265 * This function doesn't touch a filesystem, it looks solely at path names.
267 * In fact, filename might not be absolute, in which case, FALSE.
268 * Ditto basedir.
270 * To belabor the intent:
271 * /1/2/3 is considered to be in /1/2
272 * /1/2/3/4 is not considered be in /1/2
274 * Besides a "slash sensitive" prefix match, also check for
275 * additional slashes.
277 * "Slash sensitive" prefix match means:
278 * /a/b is a prefix of /a/b/
279 * /a/b is not a prefix of /a/bc
280 * /a/b is maybe a prefix of /a/b
281 * The string being checked against must either end, or continue with a path separator.
282 * "Normal" prefix matching would be true for both.
284 * This function also considers runs of slashes to be equivalent to single slashes,
285 * which is generally Windows behavior, except at the start of a path.
287 gboolean
288 mono_path_filename_in_basedir (const char *filename, const char *basedir)
290 g_assert (filename);
291 g_assert (basedir);
293 size_t filename_length = strlen (filename);
294 size_t basedir_length = strlen (basedir);
296 if (!mono_path_contains_separator (filename, filename_length))
297 return FALSE;
298 if (!mono_path_contains_separator (basedir, basedir_length))
299 return FALSE;
300 //g_assertf (mono_path_contains_separator (filename, filename_length), "filename:%s basedir:%s", filename, basedir);
301 //g_assertf (mono_path_contains_separator (basedir, basedir_length), "filename:%s basedir:%s", filename, basedir);
303 mono_path_remove_trailing_path_separators (filename, &filename_length);
304 mono_path_remove_trailing_path_separators (basedir, &basedir_length);
306 // basedir_length can be 0 at this point and that is ok.
308 if (!filename_length
309 || filename_length <= basedir_length
310 || (basedir_length && !mono_path_equal (filename, basedir, basedir_length)))
311 return FALSE;
313 // /foo/1 is in /foo.
314 // /foo//1 is in /foo.
315 // /foo/1/ is in /foo.
316 // /foo//1/ is in /foo.
317 // /foo//1// is in /foo.
319 // /foo is not in /foo.
320 // /foo/ is not in /foo.
321 // /foob is not in /foo.
322 // /foo/1/2 is not in /foo.
324 // Skip basedir's length within filename.
325 const char *after_base = &filename [basedir_length];
326 size_t after_base_length = filename_length - basedir_length;
328 // Skip any number of slashes.
329 size_t skip_separators = mono_path_path_separator_length (after_base, after_base_length);
330 after_base += skip_separators;
331 after_base_length -= skip_separators;
333 // There must been at least one slash, and then after any non-slashes,
334 // there must not be any more slashes.
335 return skip_separators && !mono_path_contains_separator (after_base, after_base_length);