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