3 * Routines for handling path names.
6 * Gonzalo Paniagua Javier (gonzalo@novell.com)
7 * Miguel de Icaza (miguel@novell.com)
9 * (C) 2006 Novell, Inc. http://www.novell.com
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>
25 #include "mono-path.h"
27 /* Embedded systems lack MAXSYMLINKS */
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 */
40 mono_path_canonicalize (const char *path
)
42 gchar
*abspath
, *pos
, *lastpos
, *dest
;
45 if (g_path_is_absolute (path
)) {
46 abspath
= g_strdup (path
);
48 gchar
*tmpdir
= g_get_current_dir ();
49 abspath
= g_build_filename (tmpdir
, path
, (const char*)NULL
);
54 g_strdelimit (abspath
, '/', '\\');
56 abspath
= g_strreverse (abspath
);
59 dest
= lastpos
= abspath
;
60 pos
= strchr (lastpos
, G_DIR_SEPARATOR
);
63 int len
= pos
- lastpos
;
64 if (len
== 1 && lastpos
[0] == '.') {
66 } else if (len
== 2 && lastpos
[0] == '.' && lastpos
[1] == '.') {
73 /* The two strings can overlap */
74 memmove (dest
, lastpos
, len
+ 1);
79 pos
= strchr (lastpos
, G_DIR_SEPARATOR
);
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)
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
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
;
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)
113 resolve_symlink (const char *path
)
115 char *p
, *concat
, *dir
;
116 char buffer
[PATH_MAX
+1];
117 int n
, iterations
= 0;
122 n
= readlink (p
, buffer
, sizeof (buffer
)-1);
125 p
= mono_path_canonicalize (copy
);
131 if (!g_path_is_absolute (buffer
)) {
132 dir
= g_path_get_dirname (p
);
133 concat
= g_build_filename (dir
, buffer
, (const char*)NULL
);
136 concat
= g_strdup (buffer
);
139 p
= mono_path_canonicalize (concat
);
141 } while (iterations
< MAXSYMLINKS
);
148 mono_path_resolve_symlinks (const char *path
)
150 #if defined(HOST_NO_SYMLINKS)
151 return mono_path_canonicalize (path
);
153 gchar
**split
= g_strsplit (path
, G_DIR_SEPARATOR_S
, -1);
154 gchar
*p
= g_strdup ("");
157 for (i
= 0; split
[i
] != NULL
; i
++) {
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
]);
164 p
= resolve_symlink (tmp
);
168 if (split
[i
+1] != NULL
) {
169 tmp
= g_strdup_printf ("%s%s", p
, G_DIR_SEPARATOR_S
);
181 mono_path_char_is_separator (char ch
)
184 return ch
== '/' || ch
== '\\';
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
]))
201 mono_path_remove_trailing_path_separators (const char *path
, size_t *length
)
204 while (i
> 0 && mono_path_char_is_separator (path
[i
- 1]))
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.
220 mono_path_char_upcase (char a
)
222 return mono_path_char_is_lowercase (a
) ? (char)(a
- 'a' + 'A') : a
;
226 mono_path_char_equal (char a
, char 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
));
236 mono_path_equal (const char *a
, const char *b
, size_t length
)
240 for (i
= 0; i
< length
&& mono_path_char_equal (a
[i
], b
[i
]); ++i
) {
245 return memcmp (a
, b
, length
) == 0;
250 mono_path_path_separator_length (const char *a
, size_t length
)
253 while (i
< length
&& mono_path_char_is_separator (a
[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.
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.
288 mono_path_filename_in_basedir (const char *filename
, const char *basedir
)
293 size_t filename_length
= strlen (filename
);
294 size_t basedir_length
= strlen (basedir
);
296 if (!mono_path_contains_separator (filename
, filename_length
))
298 if (!mono_path_contains_separator (basedir
, basedir_length
))
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.
309 || filename_length
<= basedir_length
310 || (basedir_length
&& !mono_path_equal (filename
, basedir
, basedir_length
)))
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
);