f423096a7800194c73af530b756e897f9b8050c9
[mono.git] / mono / utils / mono-io-portability.c
1 #include "config.h"
2
3 #include <string.h>
4 #ifdef HAVE_UNISTD_H
5 #include <unistd.h>
6 #endif
7 #include <errno.h>
8 #include <mono/utils/mono-io-portability.h>
9 #include <mono/metadata/profiler-private.h>
10
11 #ifndef DISABLE_PORTABILITY
12
13 #include <dirent.h>
14
15 int mono_io_portability_helpers = PORTABILITY_UNKNOWN;
16
17 static inline gchar *mono_portability_find_file_internal (GString **report, const gchar *pathname, gboolean last_exists);
18
19 void mono_portability_helpers_init (void)
20 {
21         const gchar *env;
22
23         if (mono_io_portability_helpers != PORTABILITY_UNKNOWN)
24                 return;
25         
26         mono_io_portability_helpers = PORTABILITY_NONE;
27         
28         env = g_getenv ("MONO_IOMAP");
29         if (env != NULL) {
30                 /* parse the environment setting and set up some vars
31                  * here
32                  */
33                 gchar **options = g_strsplit (env, ":", 0);
34                 int i;
35                 
36                 if (options == NULL) {
37                         /* This shouldn't happen */
38                         return;
39                 }
40                 
41                 for (i = 0; options[i] != NULL; i++) {
42 #ifdef DEBUG
43                         g_message ("%s: Setting option [%s]", __func__,
44                                    options[i]);
45 #endif
46                         if (!strncasecmp (options[i], "drive", 5)) {
47                                 mono_io_portability_helpers |= PORTABILITY_DRIVE;
48                         } else if (!strncasecmp (options[i], "case", 4)) {
49                                 mono_io_portability_helpers |= PORTABILITY_CASE;
50                         } else if (!strncasecmp (options[i], "all", 3)) {
51                                 mono_io_portability_helpers |= (PORTABILITY_DRIVE | PORTABILITY_CASE);
52                         }
53                 }
54         }
55 }
56
57 /* Returns newly allocated string, or NULL on failure */
58 static gchar *find_in_dir (DIR *current, const gchar *name)
59 {
60         struct dirent *entry;
61
62 #ifdef DEBUG
63         g_message ("%s: looking for [%s]\n", __func__, name);
64 #endif
65         
66         while((entry = readdir (current)) != NULL) {
67 #ifdef DEBUGX
68                 g_message ("%s: found [%s]\n", __func__, entry->d_name);
69 #endif
70                 
71                 if (!g_ascii_strcasecmp (name, entry->d_name)) {
72                         char *ret;
73                         
74 #ifdef DEBUG
75                         g_message ("%s: matched [%s] to [%s]\n", __func__,
76                                    entry->d_name, name);
77 #endif
78
79                         ret = g_strdup (entry->d_name);
80                         closedir (current);
81                         return ret;
82                 }
83         }
84         
85 #ifdef DEBUG
86         g_message ("%s: returning NULL\n", __func__);
87 #endif
88         
89         closedir (current);
90         
91         return(NULL);
92 }
93
94 static inline void append_report (GString **report, const gchar *format, ...)
95 {
96         va_list ap;
97         if (!*report)
98                 *report = g_string_new ("");
99
100         va_start (ap, format);
101         g_string_append_vprintf (*report, format, ap);
102         va_end (ap);
103 }
104
105 static inline void do_mono_profiler_iomap (GString **report, const char *pathname, const char *new_pathname)
106 {
107         char *rep = NULL;
108         GString *tmp = report ? *report : NULL;
109
110         if (tmp) {
111                 if (tmp->len > 0)
112                         rep = g_string_free (tmp, FALSE);
113                 else
114                         g_string_free (tmp, TRUE);
115                 *report = NULL;
116         }
117
118         mono_profiler_iomap (rep, pathname, new_pathname);
119         g_free (rep);
120 }
121
122 gchar *mono_portability_find_file (const gchar *pathname, gboolean last_exists)
123 {
124         GString *report = NULL;
125         gchar *ret;
126         
127         if (!pathname || !pathname [0])
128                 return NULL;
129         ret = mono_portability_find_file_internal (&report, pathname, last_exists);
130
131         if (report)
132                 g_string_free (report, TRUE);
133
134         return ret;
135 }
136
137 /* Returns newly-allocated string or NULL on failure */
138 static inline gchar *mono_portability_find_file_internal (GString **report, const gchar *pathname, gboolean last_exists)
139 {
140         gchar *new_pathname, **components, **new_components;
141         int num_components = 0, component = 0;
142         DIR *scanning = NULL;
143         size_t len;
144         gboolean drive_stripped = FALSE;
145         gboolean do_report = (mono_profiler_get_events () & MONO_PROFILE_IOMAP_EVENTS) != 0;
146
147         if (IS_PORTABILITY_NONE) {
148                 return(NULL);
149         }
150
151         if (do_report)
152                 append_report (report, " - Requested file path: '%s'\n", pathname);
153
154         new_pathname = g_strdup (pathname);
155         
156 #ifdef DEBUG
157         g_message ("%s: Finding [%s] last_exists: %s\n", __func__, pathname,
158                    last_exists?"TRUE":"FALSE");
159 #endif
160         
161         if (last_exists &&
162             access (new_pathname, F_OK) == 0) {
163 #ifdef DEBUG
164                 g_message ("%s: Found it without doing anything\n", __func__);
165 #endif
166                 return(new_pathname);
167         }
168         
169         /* First turn '\' into '/' and strip any drive letters */
170         g_strdelimit (new_pathname, "\\", '/');
171
172 #ifdef DEBUG
173         g_message ("%s: Fixed slashes, now have [%s]\n", __func__,
174                    new_pathname);
175 #endif
176         
177         if (IS_PORTABILITY_DRIVE &&
178             g_ascii_isalpha (new_pathname[0]) &&
179             (new_pathname[1] == ':')) {
180                 int len = strlen (new_pathname);
181                 
182                 g_memmove (new_pathname, new_pathname+2, len - 2);
183                 new_pathname[len - 2] = '\0';
184
185                 if (do_report) {
186                         append_report (report, " - Stripped drive letter.\n");
187                         drive_stripped = TRUE;
188                 }
189 #ifdef DEBUG
190                 g_message ("%s: Stripped drive letter, now looking for [%s]\n",
191                            __func__, new_pathname);
192 #endif
193         }
194
195         len = strlen (new_pathname);
196         if (len > 1 && new_pathname [len - 1] == '/') {
197                 new_pathname [len - 1] = 0;
198 #ifdef DEBUG
199                 g_message ("%s: requested name had a trailing /, rewritten to '%s'\n",
200                            __func__, new_pathname);
201 #endif
202         }
203
204         if (last_exists &&
205             access (new_pathname, F_OK) == 0) {
206 #ifdef DEBUG
207                 g_message ("%s: Found it\n", __func__);
208 #endif
209                 if (do_report && drive_stripped)
210                         do_mono_profiler_iomap (report, pathname, new_pathname);
211
212                 return(new_pathname);
213         }
214
215         /* OK, have to work harder.  Take each path component in turn
216          * and do a case-insensitive directory scan for it
217          */
218
219         if (!(IS_PORTABILITY_CASE)) {
220                 g_free (new_pathname);
221                 return(NULL);
222         }
223
224         components = g_strsplit (new_pathname, "/", 0);
225         if (components == NULL) {
226                 /* This shouldn't happen */
227                 g_free (new_pathname);
228                 return(NULL);
229         }
230         
231         while(components[num_components] != NULL) {
232                 num_components++;
233         }
234         g_free (new_pathname);
235         
236         if (num_components == 0){
237                 return NULL;
238         }
239         
240
241         new_components = (gchar **)g_new0 (gchar **, num_components + 1);
242
243         if (num_components > 1) {
244                 if (strcmp (components[0], "") == 0) {
245                         /* first component blank, so start at / */
246                         scanning = opendir ("/");
247                         if (scanning == NULL) {
248 #ifdef DEBUG
249                                 g_message ("%s: opendir 1 error: %s", __func__,
250                                            g_strerror (errno));
251 #endif
252                                 g_strfreev (new_components);
253                                 g_strfreev (components);
254                                 return(NULL);
255                         }
256                 
257                         new_components[component++] = g_strdup ("");
258                 } else {
259                         DIR *current;
260                         gchar *entry;
261                 
262                         current = opendir (".");
263                         if (current == NULL) {
264 #ifdef DEBUG
265                                 g_message ("%s: opendir 2 error: %s", __func__,
266                                            g_strerror (errno));
267 #endif
268                                 g_strfreev (new_components);
269                                 g_strfreev (components);
270                                 return(NULL);
271                         }
272                 
273                         entry = find_in_dir (current, components[0]);
274                         if (entry == NULL) {
275                                 g_strfreev (new_components);
276                                 g_strfreev (components);
277                                 return(NULL);
278                         }
279                 
280                         scanning = opendir (entry);
281                         if (scanning == NULL) {
282 #ifdef DEBUG
283                                 g_message ("%s: opendir 3 error: %s", __func__,
284                                            g_strerror (errno));
285 #endif
286                                 g_free (entry);
287                                 g_strfreev (new_components);
288                                 g_strfreev (components);
289                                 return(NULL);
290                         }
291                 
292                         new_components[component++] = entry;
293                 }
294         } else {
295                 if (last_exists) {
296                         if (strcmp (components[0], "") == 0) {
297                                 /* First and only component blank */
298                                 new_components[component++] = g_strdup ("");
299                         } else {
300                                 DIR *current;
301                                 gchar *entry;
302                                 
303                                 current = opendir (".");
304                                 if (current == NULL) {
305 #ifdef DEBUG
306                                         g_message ("%s: opendir 4 error: %s",
307                                                    __func__,
308                                                    g_strerror (errno));
309 #endif
310                                         g_strfreev (new_components);
311                                         g_strfreev (components);
312                                         return(NULL);
313                                 }
314                                 
315                                 entry = find_in_dir (current, components[0]);
316                                 if (entry == NULL) {
317                                         g_strfreev (new_components);
318                                         g_strfreev (components);
319                                         return(NULL);
320                                 }
321                                 
322                                 new_components[component++] = entry;
323                         }
324                 } else {
325                                 new_components[component++] = g_strdup (components[0]);
326                 }
327         }
328
329 #ifdef DEBUG
330         g_message ("%s: Got first entry: [%s]\n", __func__, new_components[0]);
331 #endif
332
333         g_assert (component == 1);
334         
335         for(; component < num_components; component++) {
336                 gchar *entry;
337                 gchar *path_so_far;
338                 
339                 if (!last_exists &&
340                     component == num_components -1) {
341                         entry = g_strdup (components[component]);
342                         closedir (scanning);
343                 } else {
344                         entry = find_in_dir (scanning, components[component]);
345                         if (entry == NULL) {
346                                 g_strfreev (new_components);
347                                 g_strfreev (components);
348                                 return(NULL);
349                         }
350                 }
351                 
352                 new_components[component] = entry;
353                 
354                 if (component < num_components -1) {
355                         path_so_far = g_strjoinv ("/", new_components);
356
357                         scanning = opendir (path_so_far);
358                         g_free (path_so_far);
359                         if (scanning == NULL) {
360                                 g_strfreev (new_components);
361                                 g_strfreev (components);
362                                 return(NULL);
363                         }
364                 }
365         }
366         
367         g_strfreev (components);
368
369         new_pathname = g_strjoinv ("/", new_components);
370
371 #ifdef DEBUG
372         g_message ("%s: pathname [%s] became [%s]\n", __func__, pathname,
373                    new_pathname);
374 #endif
375         
376         g_strfreev (new_components);
377
378         if ((last_exists &&
379              access (new_pathname, F_OK) == 0) ||
380             (!last_exists)) {
381                 if (do_report && strcmp (pathname, new_pathname) != 0)
382                         do_mono_profiler_iomap (report, pathname, new_pathname);
383
384                 return(new_pathname);
385         }
386
387         g_free (new_pathname);
388         return(NULL);
389 }
390
391 #else /* DISABLE_PORTABILITY */
392
393 #ifdef _MSC_VER
394 // Quiet Visual Studio linker warning, LNK4221, in cases when this source file intentional ends up empty.
395 void __mono_win32_mono_io_portability_quiet_lnk4221(void) {}
396 #endif
397 #endif /* DISABLE_PORTABILITY */