In corlib/System.Runtime.InteropServices:
[mono.git] / mono / io-layer / io-portability.c
1 /*
2  * io-portability.c:  Optional filename mangling to try to cope with
3  *                      badly-written non-portable windows apps
4  *
5  * Author:
6  *      Dick Porter (dick@ximian.com)
7  *
8  * Copyright (c) 2006 Novell, Inc.
9  */
10
11 #include <config.h>
12 #include <glib.h>
13 #include <stdio.h>
14 #include <sys/stat.h>
15 #include <fcntl.h>
16 #include <errno.h>
17 #include <string.h>
18 #include <unistd.h>
19 #include <stdlib.h>
20 #include <sys/types.h>
21 #include <dirent.h>
22 #include <utime.h>
23 #include <sys/stat.h>
24
25 #include <mono/io-layer/mono-mutex.h>
26 #include <mono/io-layer/io-portability.h>
27
28 #undef DEBUG
29
30 enum {
31         PORTABILITY_NONE        = 0x00,
32         PORTABILITY_UNKNOWN     = 0x01,
33         PORTABILITY_DRIVE       = 0x02,
34         PORTABILITY_CASE        = 0x04,
35 };
36
37 static mono_once_t options_once = MONO_ONCE_INIT;
38 static int portability_helpers = PORTABILITY_UNKNOWN;
39
40 static void options_init (void)
41 {
42         const gchar *env;
43         
44         portability_helpers = PORTABILITY_NONE;
45         
46         env = g_getenv ("MONO_IOMAP");
47         if (env != NULL) {
48                 /* parse the environment setting and set up some vars
49                  * here
50                  */
51                 gchar **options = g_strsplit (env, ":", 0);
52                 int i;
53                 
54                 if (options == NULL) {
55                         /* This shouldn't happen */
56                         return;
57                 }
58                 
59                 for (i = 0; options[i] != NULL; i++) {
60 #ifdef DEBUG
61                         g_message ("%s: Setting option [%s]", __func__,
62                                    options[i]);
63 #endif
64                         if (!strncasecmp (options[i], "drive", 5)) {
65                                 portability_helpers |= PORTABILITY_DRIVE;
66                         } else if (!strncasecmp (options[i], "case", 4)) {
67                                 portability_helpers |= PORTABILITY_CASE;
68                         } else if (!strncasecmp (options[i], "all", 3)) {
69                                 portability_helpers |= (PORTABILITY_DRIVE |
70                                                         PORTABILITY_CASE);
71                         }
72                 }
73         }
74 }
75
76 /* Returns newly allocated string, or NULL on failure */
77 static gchar *find_in_dir (DIR *current, const gchar *name)
78 {
79         struct dirent *entry;
80
81 #ifdef DEBUG
82         g_message ("%s: looking for [%s]\n", __func__, name);
83 #endif
84         
85         while((entry = readdir (current)) != NULL) {
86 #ifdef DEBUGX
87                 g_message ("%s: found [%s]\n", __func__, entry->d_name);
88 #endif
89                 
90                 if (!g_ascii_strcasecmp (name, entry->d_name)) {
91                         char *ret;
92                         
93 #ifdef DEBUG
94                         g_message ("%s: matched [%s] to [%s]\n", __func__,
95                                    entry->d_name, name);
96 #endif
97
98                         ret = g_strdup (entry->d_name);
99                         closedir (current);
100                         return ret;
101                 }
102         }
103         
104 #ifdef DEBUG
105         g_message ("%s: returning NULL\n", __func__);
106 #endif
107         
108         closedir (current);
109         
110         return(NULL);
111 }
112
113 /* Returns newly-allocated string or NULL on failure */
114 static gchar *find_file (const gchar *pathname, gboolean last_exists)
115 {
116         gchar *new_pathname, **components, **new_components;
117         int num_components = 0, component = 0;
118         DIR *scanning = NULL;
119         
120         mono_once (&options_once, options_init);
121
122         if (portability_helpers == PORTABILITY_NONE) {
123                 return(NULL);
124         }
125
126         new_pathname = g_strdup (pathname);
127         
128 #ifdef DEBUG
129         g_message ("%s: Finding [%s] last_exists: %s\n", __func__, pathname,
130                    last_exists?"TRUE":"FALSE");
131 #endif
132         
133         if (last_exists &&
134             access (new_pathname, F_OK) == 0) {
135 #ifdef DEBUG
136                 g_message ("%s: Found it without doing anything\n", __func__);
137 #endif
138                 return(new_pathname);
139         }
140         
141         /* First turn '\' into '/' and strip any drive letters */
142         g_strdelimit (new_pathname, "\\", '/');
143
144 #ifdef DEBUG
145         g_message ("%s: Fixed slashes, now have [%s]\n", __func__,
146                    new_pathname);
147 #endif
148         
149         if (portability_helpers & PORTABILITY_DRIVE &&
150             g_ascii_isalpha (new_pathname[0]) &&
151             (new_pathname[1] == ':')) {
152                 int len = strlen (new_pathname);
153                 
154                 g_memmove (new_pathname, new_pathname+2, len - 2);
155                 new_pathname[len - 2] = '\0';
156                 
157 #ifdef DEBUG
158                 g_message ("%s: Stripped drive letter, now looking for [%s]\n",
159                            __func__, new_pathname);
160 #endif
161         }
162         
163         if (last_exists &&
164             access (new_pathname, F_OK) == 0) {
165 #ifdef DEBUG
166                 g_message ("%s: Found it\n", __func__);
167 #endif
168                 
169                 return(new_pathname);
170         }
171
172         /* OK, have to work harder.  Take each path component in turn
173          * and do a case-insensitive directory scan for it
174          */
175
176         if (!(portability_helpers & PORTABILITY_CASE)) {
177                 g_free (new_pathname);
178                 return(NULL);
179         }
180
181         components = g_strsplit (new_pathname, "/", 0);
182         if (components == NULL) {
183                 /* This shouldn't happen */
184                 g_free (new_pathname);
185                 return(NULL);
186         }
187         
188         while(components[num_components] != NULL) {
189                 num_components++;
190         }
191         g_free (new_pathname);
192         
193         if (num_components == 0){
194                 return NULL;
195         }
196         
197
198         new_components = (gchar **)g_new0 (gchar **, num_components + 1);
199
200         if (num_components > 1) {
201                 if (strcmp (components[0], "") == 0) {
202                         /* first component blank, so start at / */
203                         scanning = opendir ("/");
204                         if (scanning == NULL) {
205 #ifdef DEBUG
206                                 g_message ("%s: opendir 1 error: %s", __func__,
207                                            g_strerror (errno));
208 #endif
209                                 g_strfreev (new_components);
210                                 g_strfreev (components);
211                                 return(NULL);
212                         }
213                 
214                         new_components[component++] = g_strdup ("");
215                 } else {
216                         DIR *current;
217                         gchar *entry;
218                 
219                         current = opendir (".");
220                         if (current == NULL) {
221 #ifdef DEBUG
222                                 g_message ("%s: opendir 2 error: %s", __func__,
223                                            g_strerror (errno));
224 #endif
225                                 g_strfreev (new_components);
226                                 g_strfreev (components);
227                                 return(NULL);
228                         }
229                 
230                         entry = find_in_dir (current, components[0]);
231                         if (entry == NULL) {
232                                 g_strfreev (new_components);
233                                 g_strfreev (components);
234                                 return(NULL);
235                         }
236                 
237                         scanning = opendir (entry);
238                         if (scanning == NULL) {
239 #ifdef DEBUG
240                                 g_message ("%s: opendir 3 error: %s", __func__,
241                                            g_strerror (errno));
242 #endif
243                                 g_free (entry);
244                                 g_strfreev (new_components);
245                                 g_strfreev (components);
246                                 return(NULL);
247                         }
248                 
249                         new_components[component++] = entry;
250                 }
251         } else {
252                 if (last_exists) {
253                         if (strcmp (components[0], "") == 0) {
254                                 /* First and only component blank */
255                                 new_components[component++] = g_strdup ("");
256                         } else {
257                                 DIR *current;
258                                 gchar *entry;
259                                 
260                                 current = opendir (".");
261                                 if (current == NULL) {
262 #ifdef DEBUG
263                                         g_message ("%s: opendir 4 error: %s",
264                                                    __func__,
265                                                    g_strerror (errno));
266 #endif
267                                         g_strfreev (new_components);
268                                         g_strfreev (components);
269                                         return(NULL);
270                                 }
271                                 
272                                 entry = find_in_dir (current, components[0]);
273                                 if (entry == NULL) {
274                                         g_strfreev (new_components);
275                                         g_strfreev (components);
276                                         return(NULL);
277                                 }
278                                 
279                                 new_components[component++] = entry;
280                         }
281                 } else {
282                                 new_components[component++] = g_strdup (components[0]);
283                 }
284         }
285
286 #ifdef DEBUG
287         g_message ("%s: Got first entry: [%s]\n", __func__, new_components[0]);
288 #endif
289
290         g_assert (component == 1);
291         
292         for(; component < num_components; component++) {
293                 gchar *entry;
294                 gchar *path_so_far;
295                 
296                 if (!last_exists &&
297                     component == num_components -1) {
298                         entry = g_strdup (components[component]);
299                         closedir (scanning);
300                 } else {
301                         entry = find_in_dir (scanning, components[component]);
302                         if (entry == NULL) {
303                                 g_strfreev (new_components);
304                                 g_strfreev (components);
305                                 return(NULL);
306                         }
307                 }
308                 
309                 new_components[component] = entry;
310                 
311                 if (component < num_components -1) {
312                         path_so_far = g_strjoinv ("/", new_components);
313
314                         scanning = opendir (path_so_far);
315                         g_free (path_so_far);
316                         if (scanning == NULL) {
317                                 g_strfreev (new_components);
318                                 g_strfreev (components);
319                                 return(NULL);
320                         }
321                 }
322         }
323         
324         g_strfreev (components);
325
326         new_pathname = g_strjoinv ("/", new_components);
327
328 #ifdef DEBUG
329         g_message ("%s: pathname [%s] became [%s]\n", __func__, pathname,
330                    new_pathname);
331 #endif
332         
333         g_strfreev (new_components);
334
335         if ((last_exists &&
336              access (new_pathname, F_OK) == 0) ||
337             (!last_exists)) {
338                 return(new_pathname);
339         }
340         
341         g_free (new_pathname);
342         return(NULL);
343 }
344
345 int _wapi_open (const char *pathname, int flags, mode_t mode)
346 {
347         int fd;
348         gchar *located_filename;
349         
350         if (flags & O_CREAT) {
351                 located_filename = find_file (pathname, FALSE);
352                 if (located_filename == NULL) {
353                         fd = open (pathname, flags, mode);
354                 } else {
355                         fd = open (located_filename, flags, mode);
356                         g_free (located_filename);
357                 }
358         } else {
359                 fd = open (pathname, flags, mode);
360                 if (fd == -1 &&
361                     (errno == ENOENT || errno == ENOTDIR) &&
362                     portability_helpers > 0) {
363                         int saved_errno = errno;
364                         located_filename = find_file (pathname, TRUE);
365                         
366                         if (located_filename == NULL) {
367                                 errno = saved_errno;
368                                 return (-1);
369                         }
370                         
371                         fd = open (located_filename, flags, mode);
372                         g_free (located_filename);
373                 }
374         }
375         
376         
377         return(fd);
378 }
379
380 int _wapi_access (const char *pathname, int mode)
381 {
382         int ret;
383         
384         ret = access (pathname, mode);
385         if (ret == -1 &&
386             (errno == ENOENT || errno == ENOTDIR) &&
387             portability_helpers > 0) {
388                 int saved_errno = errno;
389                 gchar *located_filename = find_file (pathname, TRUE);
390                 
391                 if (located_filename == NULL) {
392                         errno = saved_errno;
393                         return(-1);
394                 }
395                 
396                 ret = access (located_filename, mode);
397                 g_free (located_filename);
398         }
399         
400         return(ret);
401 }
402
403 int _wapi_chmod (const char *pathname, mode_t mode)
404 {
405         int ret;
406         
407         ret = chmod (pathname, mode);
408         if (ret == -1 &&
409             (errno == ENOENT || errno == ENOTDIR) &&
410             portability_helpers > 0) {
411                 int saved_errno = errno;
412                 gchar *located_filename = find_file (pathname, TRUE);
413                 
414                 if (located_filename == NULL) {
415                         errno = saved_errno;
416                         return(-1);
417                 }
418                 
419                 ret = chmod (located_filename, mode);
420                 g_free (located_filename);
421         }
422         
423         return(ret);
424 }
425
426 int _wapi_utime (const char *filename, const struct utimbuf *buf)
427 {
428         int ret;
429         
430         ret = utime (filename, buf);
431         if (ret == -1 &&
432             errno == ENOENT &&
433             portability_helpers > 0) {
434                 int saved_errno = errno;
435                 gchar *located_filename = find_file (filename, TRUE);
436                 
437                 if (located_filename == NULL) {
438                         errno = saved_errno;
439                         return(-1);
440                 }
441                 
442                 ret = utime (located_filename, buf);
443                 g_free (located_filename);
444         }
445         
446         return(ret);
447 }
448
449 int _wapi_unlink (const char *pathname)
450 {
451         int ret;
452         
453         ret = unlink (pathname);
454         if (ret == -1 &&
455             (errno == ENOENT || errno == ENOTDIR || errno == EISDIR) &&
456             portability_helpers > 0) {
457                 int saved_errno = errno;
458                 gchar *located_filename = find_file (pathname, TRUE);
459                 
460                 if (located_filename == NULL) {
461                         errno = saved_errno;
462                         return(-1);
463                 }
464                 
465                 ret = unlink (located_filename);
466                 g_free (located_filename);
467         }
468         
469         return(ret);
470 }
471
472 int _wapi_rename (const char *oldpath, const char *newpath)
473 {
474         int ret;
475         gchar *located_newpath = find_file (newpath, FALSE);
476         
477         if (located_newpath == NULL) {
478                 ret = rename (oldpath, newpath);
479         } else {
480                 ret = rename (oldpath, located_newpath);
481         
482                 if (ret == -1 &&
483                     (errno == EISDIR || errno == ENAMETOOLONG ||
484                      errno == ENOENT || errno == ENOTDIR || errno == EXDEV) &&
485                     portability_helpers > 0) {
486                         int saved_errno = errno;
487                         gchar *located_oldpath = find_file (oldpath, TRUE);
488                         
489                         if (located_oldpath == NULL) {
490                                 g_free (located_oldpath);
491                                 g_free (located_newpath);
492                         
493                                 errno = saved_errno;
494                                 return(-1);
495                         }
496                         
497                         ret = rename (located_oldpath, located_newpath);
498                         g_free (located_oldpath);
499                 }
500                 g_free (located_newpath);
501         }
502         
503         return(ret);
504 }
505
506 int _wapi_stat (const char *path, struct stat *buf)
507 {
508         int ret;
509         
510         ret = stat (path, buf);
511         if (ret == -1 &&
512             (errno == ENOENT || errno == ENOTDIR) &&
513             portability_helpers > 0) {
514                 int saved_errno = errno;
515                 gchar *located_filename = find_file (path, TRUE);
516                 
517                 if (located_filename == NULL) {
518                         errno = saved_errno;
519                         return(-1);
520                 }
521                 
522                 ret = stat (located_filename, buf);
523                 g_free (located_filename);
524         }
525         
526         return(ret);
527 }
528
529 int _wapi_lstat (const char *path, struct stat *buf)
530 {
531         int ret;
532         
533         ret = lstat (path, buf);
534         if (ret == -1 &&
535             (errno == ENOENT || errno == ENOTDIR) &&
536             portability_helpers > 0) {
537                 int saved_errno = errno;
538                 gchar *located_filename = find_file (path, TRUE);
539                 
540                 if (located_filename == NULL) {
541                         errno = saved_errno;
542                         return(-1);
543                 }
544                 
545                 ret = lstat (located_filename, buf);
546                 g_free (located_filename);
547         }
548         
549         return(ret);
550 }
551
552 int _wapi_mkdir (const char *pathname, mode_t mode)
553 {
554         int ret;
555         gchar *located_filename = find_file (pathname, FALSE);
556         
557         if (located_filename == NULL) {
558                 ret = mkdir (pathname, mode);
559         } else {
560                 ret = mkdir (located_filename, mode);
561                 g_free (located_filename);
562         }
563         
564         return(ret);
565 }
566
567 int _wapi_rmdir (const char *pathname)
568 {
569         int ret;
570         
571         ret = rmdir (pathname);
572         if (ret == -1 &&
573             (errno == ENOENT || errno == ENOTDIR || errno == ENAMETOOLONG) &&
574             portability_helpers > 0) {
575                 int saved_errno = errno;
576                 gchar *located_filename = find_file (pathname, TRUE);
577                 
578                 if (located_filename == NULL) {
579                         errno = saved_errno;
580                         return(-1);
581                 }
582                 
583                 ret = rmdir (located_filename);
584                 g_free (located_filename);
585         }
586         
587         return(ret);
588 }
589
590 int _wapi_chdir (const char *path)
591 {
592         int ret;
593         
594         ret = chdir (path);
595         if (ret == -1 &&
596             (errno == ENOENT || errno == ENOTDIR || errno == ENAMETOOLONG) &&
597             portability_helpers > 0) {
598                 int saved_errno = errno;
599                 gchar *located_filename = find_file (path, TRUE);
600                 
601                 if (located_filename == NULL) {
602                         errno = saved_errno;
603                         return(-1);
604                 }
605                 
606                 ret = chdir (located_filename);
607                 g_free (located_filename);
608         }
609         
610         return(ret);
611 }
612
613 gchar *_wapi_basename (const gchar *filename)
614 {
615         gchar *new_filename = g_strdup (filename), *ret;
616
617         mono_once (&options_once, options_init);
618         
619         g_strdelimit (new_filename, "\\", '/');
620
621         if (portability_helpers & PORTABILITY_DRIVE &&
622             g_ascii_isalpha (new_filename[0]) &&
623             (new_filename[1] == ':')) {
624                 int len = strlen (new_filename);
625                 
626                 g_memmove (new_filename, new_filename + 2, len - 2);
627                 new_filename[len - 2] = '\0';
628         }
629         
630         ret = g_path_get_basename (new_filename);
631         g_free (new_filename);
632         
633         return(ret);
634 }
635
636 gchar *_wapi_dirname (const gchar *filename)
637 {
638         gchar *new_filename = g_strdup (filename), *ret;
639
640         mono_once (&options_once, options_init);
641         
642         g_strdelimit (new_filename, "\\", '/');
643
644         if (portability_helpers & PORTABILITY_DRIVE &&
645             g_ascii_isalpha (new_filename[0]) &&
646             (new_filename[1] == ':')) {
647                 int len = strlen (new_filename);
648                 
649                 g_memmove (new_filename, new_filename + 2, len - 2);
650                 new_filename[len - 2] = '\0';
651         }
652         
653         ret = g_path_get_dirname (new_filename);
654         g_free (new_filename);
655         
656         return(ret);
657 }
658
659 GDir *_wapi_g_dir_open (const gchar *path, guint flags, GError **error)
660 {
661         GDir *ret;
662         
663         ret = g_dir_open (path, flags, error);
664         if (ret == NULL &&
665             ((*error)->code == G_FILE_ERROR_NOENT ||
666              (*error)->code == G_FILE_ERROR_NOTDIR ||
667              (*error)->code == G_FILE_ERROR_NAMETOOLONG) &&
668             portability_helpers > 0) {
669                 gchar *located_filename = find_file (path, TRUE);
670                 GError *tmp_error;
671                 
672                 if (located_filename == NULL) {
673                         return(NULL);
674                 }
675                 
676                 ret = g_dir_open (located_filename, flags, &tmp_error);
677                 g_free (located_filename);
678                 if (tmp_error != NULL) {
679                         g_propagate_error (error, tmp_error);
680                 }
681         }
682         
683         return(ret);
684 }