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