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 
mono_portability_helpers_init(void)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 */
find_in_dir(DIR * current,const gchar * name)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 
append_report(GString ** report,const gchar * format,...)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 
do_mono_profiler_iomap(GString ** report,const char * pathname,const char * new_pathname)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_RAISE (iomap_report, (rep, pathname, new_pathname));
125 	g_free (rep);
126 }
127 
mono_portability_find_file(const gchar * pathname,gboolean last_exists)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 */
mono_portability_find_file_internal(GString ** report,const gchar * pathname,gboolean last_exists)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_ENABLED (iomap_report);
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 */
402