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