Merge pull request #1410 from alesliehughes/master
[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 #if defined (_EGLIB_MAJOR) || GLIB_CHECK_VERSION(2,14,0)
97         va_list ap;
98         if (!*report)
99                 *report = g_string_new ("");
100
101         va_start (ap, format);
102         g_string_append_vprintf (*report, format, ap);
103         va_end (ap);
104 #else
105         g_assert_not_reached ();
106 #endif
107 }
108
109 static inline void do_mono_profiler_iomap (GString **report, const char *pathname, const char *new_pathname)
110 {
111         char *rep = NULL;
112         GString *tmp = report ? *report : NULL;
113
114         if (tmp) {
115                 if (tmp->len > 0)
116                         rep = g_string_free (tmp, FALSE);
117                 else
118                         g_string_free (tmp, TRUE);
119                 *report = NULL;
120         }
121
122         mono_profiler_iomap (rep, pathname, new_pathname);
123         g_free (rep);
124 }
125
126 gchar *mono_portability_find_file (const gchar *pathname, gboolean last_exists)
127 {
128         GString *report = NULL;
129         gchar *ret;
130         
131         if (!pathname || !pathname [0])
132                 return NULL;
133         ret = mono_portability_find_file_internal (&report, pathname, last_exists);
134
135         if (report)
136                 g_string_free (report, TRUE);
137
138         return ret;
139 }
140
141 /* Returns newly-allocated string or NULL on failure */
142 static inline gchar *mono_portability_find_file_internal (GString **report, const gchar *pathname, gboolean last_exists)
143 {
144         gchar *new_pathname, **components, **new_components;
145         int num_components = 0, component = 0;
146         DIR *scanning = NULL;
147         size_t len;
148         gboolean drive_stripped = FALSE;
149         gboolean do_report = (mono_profiler_get_events () & MONO_PROFILE_IOMAP_EVENTS) != 0;
150
151         if (IS_PORTABILITY_NONE) {
152                 return(NULL);
153         }
154
155         if (do_report)
156                 append_report (report, " - Requested file path: '%s'\n", pathname);
157
158         new_pathname = g_strdup (pathname);
159         
160 #ifdef DEBUG
161         g_message ("%s: Finding [%s] last_exists: %s\n", __func__, pathname,
162                    last_exists?"TRUE":"FALSE");
163 #endif
164         
165         if (last_exists &&
166             access (new_pathname, F_OK) == 0) {
167 #ifdef DEBUG
168                 g_message ("%s: Found it without doing anything\n", __func__);
169 #endif
170                 return(new_pathname);
171         }
172         
173         /* First turn '\' into '/' and strip any drive letters */
174         g_strdelimit (new_pathname, "\\", '/');
175
176 #ifdef DEBUG
177         g_message ("%s: Fixed slashes, now have [%s]\n", __func__,
178                    new_pathname);
179 #endif
180         
181         if (IS_PORTABILITY_DRIVE &&
182             g_ascii_isalpha (new_pathname[0]) &&
183             (new_pathname[1] == ':')) {
184                 int len = strlen (new_pathname);
185                 
186                 g_memmove (new_pathname, new_pathname+2, len - 2);
187                 new_pathname[len - 2] = '\0';
188
189                 if (do_report) {
190                         append_report (report, " - Stripped drive letter.\n");
191                         drive_stripped = TRUE;
192                 }
193 #ifdef DEBUG
194                 g_message ("%s: Stripped drive letter, now looking for [%s]\n",
195                            __func__, new_pathname);
196 #endif
197         }
198
199         len = strlen (new_pathname);
200         if (len > 1 && new_pathname [len - 1] == '/') {
201                 new_pathname [len - 1] = 0;
202 #ifdef DEBUG
203                 g_message ("%s: requested name had a trailing /, rewritten to '%s'\n",
204                            __func__, new_pathname);
205 #endif
206         }
207
208         if (last_exists &&
209             access (new_pathname, F_OK) == 0) {
210 #ifdef DEBUG
211                 g_message ("%s: Found it\n", __func__);
212 #endif
213                 if (do_report && drive_stripped)
214                         do_mono_profiler_iomap (report, pathname, new_pathname);
215
216                 return(new_pathname);
217         }
218
219         /* OK, have to work harder.  Take each path component in turn
220          * and do a case-insensitive directory scan for it
221          */
222
223         if (!(IS_PORTABILITY_CASE)) {
224                 g_free (new_pathname);
225                 return(NULL);
226         }
227
228         components = g_strsplit (new_pathname, "/", 0);
229         if (components == NULL) {
230                 /* This shouldn't happen */
231                 g_free (new_pathname);
232                 return(NULL);
233         }
234         
235         while(components[num_components] != NULL) {
236                 num_components++;
237         }
238         g_free (new_pathname);
239         
240         if (num_components == 0){
241                 return NULL;
242         }
243         
244
245         new_components = (gchar **)g_new0 (gchar **, num_components + 1);
246
247         if (num_components > 1) {
248                 if (strcmp (components[0], "") == 0) {
249                         /* first component blank, so start at / */
250                         scanning = opendir ("/");
251                         if (scanning == NULL) {
252 #ifdef DEBUG
253                                 g_message ("%s: opendir 1 error: %s", __func__,
254                                            g_strerror (errno));
255 #endif
256                                 g_strfreev (new_components);
257                                 g_strfreev (components);
258                                 return(NULL);
259                         }
260                 
261                         new_components[component++] = g_strdup ("");
262                 } else {
263                         DIR *current;
264                         gchar *entry;
265                 
266                         current = opendir (".");
267                         if (current == NULL) {
268 #ifdef DEBUG
269                                 g_message ("%s: opendir 2 error: %s", __func__,
270                                            g_strerror (errno));
271 #endif
272                                 g_strfreev (new_components);
273                                 g_strfreev (components);
274                                 return(NULL);
275                         }
276                 
277                         entry = find_in_dir (current, components[0]);
278                         if (entry == NULL) {
279                                 g_strfreev (new_components);
280                                 g_strfreev (components);
281                                 return(NULL);
282                         }
283                 
284                         scanning = opendir (entry);
285                         if (scanning == NULL) {
286 #ifdef DEBUG
287                                 g_message ("%s: opendir 3 error: %s", __func__,
288                                            g_strerror (errno));
289 #endif
290                                 g_free (entry);
291                                 g_strfreev (new_components);
292                                 g_strfreev (components);
293                                 return(NULL);
294                         }
295                 
296                         new_components[component++] = entry;
297                 }
298         } else {
299                 if (last_exists) {
300                         if (strcmp (components[0], "") == 0) {
301                                 /* First and only component blank */
302                                 new_components[component++] = g_strdup ("");
303                         } else {
304                                 DIR *current;
305                                 gchar *entry;
306                                 
307                                 current = opendir (".");
308                                 if (current == NULL) {
309 #ifdef DEBUG
310                                         g_message ("%s: opendir 4 error: %s",
311                                                    __func__,
312                                                    g_strerror (errno));
313 #endif
314                                         g_strfreev (new_components);
315                                         g_strfreev (components);
316                                         return(NULL);
317                                 }
318                                 
319                                 entry = find_in_dir (current, components[0]);
320                                 if (entry == NULL) {
321                                         g_strfreev (new_components);
322                                         g_strfreev (components);
323                                         return(NULL);
324                                 }
325                                 
326                                 new_components[component++] = entry;
327                         }
328                 } else {
329                                 new_components[component++] = g_strdup (components[0]);
330                 }
331         }
332
333 #ifdef DEBUG
334         g_message ("%s: Got first entry: [%s]\n", __func__, new_components[0]);
335 #endif
336
337         g_assert (component == 1);
338         
339         for(; component < num_components; component++) {
340                 gchar *entry;
341                 gchar *path_so_far;
342                 
343                 if (!last_exists &&
344                     component == num_components -1) {
345                         entry = g_strdup (components[component]);
346                         closedir (scanning);
347                 } else {
348                         entry = find_in_dir (scanning, components[component]);
349                         if (entry == NULL) {
350                                 g_strfreev (new_components);
351                                 g_strfreev (components);
352                                 return(NULL);
353                         }
354                 }
355                 
356                 new_components[component] = entry;
357                 
358                 if (component < num_components -1) {
359                         path_so_far = g_strjoinv ("/", new_components);
360
361                         scanning = opendir (path_so_far);
362                         g_free (path_so_far);
363                         if (scanning == NULL) {
364                                 g_strfreev (new_components);
365                                 g_strfreev (components);
366                                 return(NULL);
367                         }
368                 }
369         }
370         
371         g_strfreev (components);
372
373         new_pathname = g_strjoinv ("/", new_components);
374
375 #ifdef DEBUG
376         g_message ("%s: pathname [%s] became [%s]\n", __func__, pathname,
377                    new_pathname);
378 #endif
379         
380         g_strfreev (new_components);
381
382         if ((last_exists &&
383              access (new_pathname, F_OK) == 0) ||
384             (!last_exists)) {
385                 if (do_report && strcmp (pathname, new_pathname) != 0)
386                         do_mono_profiler_iomap (report, pathname, new_pathname);
387
388                 return(new_pathname);
389         }
390
391         g_free (new_pathname);
392         return(NULL);
393 }
394 #endif