New test.
[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;
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_assert (num_components > 0);
192         
193         g_free (new_pathname);
194
195         new_components = (gchar **)g_new0 (gchar **, num_components + 1);
196
197         if (num_components > 1) {
198                 if (strcmp (components[0], "") == 0) {
199                         /* first component blank, so start at / */
200                         scanning = opendir ("/");
201                         if (scanning == NULL) {
202 #ifdef DEBUG
203                                 g_message ("%s: opendir 1 error: %s", __func__,
204                                            g_strerror (errno));
205 #endif
206                                 g_strfreev (new_components);
207                                 g_strfreev (components);
208                                 return(NULL);
209                         }
210                 
211                         new_components[component++] = g_strdup ("");
212                 } else {
213                         DIR *current;
214                         gchar *entry;
215                 
216                         current = opendir (".");
217                         if (current == NULL) {
218 #ifdef DEBUG
219                                 g_message ("%s: opendir 2 error: %s", __func__,
220                                            g_strerror (errno));
221 #endif
222                                 g_strfreev (new_components);
223                                 g_strfreev (components);
224                                 return(NULL);
225                         }
226                 
227                         entry = find_in_dir (current, components[0]);
228                         if (entry == NULL) {
229                                 g_strfreev (new_components);
230                                 g_strfreev (components);
231                                 return(NULL);
232                         }
233                 
234                         scanning = opendir (entry);
235                         if (scanning == NULL) {
236 #ifdef DEBUG
237                                 g_message ("%s: opendir 3 error: %s", __func__,
238                                            g_strerror (errno));
239 #endif
240                                 g_free (entry);
241                                 g_strfreev (new_components);
242                                 g_strfreev (components);
243                                 return(NULL);
244                         }
245                 
246                         new_components[component++] = entry;
247                 }
248         } else {
249                 if (last_exists) {
250                         if (strcmp (components[0], "") == 0) {
251                                 /* First and only component blank */
252                                 new_components[component++] = g_strdup ("");
253                         } else {
254                                 DIR *current;
255                                 gchar *entry;
256                                 
257                                 current = opendir (".");
258                                 if (current == NULL) {
259 #ifdef DEBUG
260                                         g_message ("%s: opendir 4 error: %s",
261                                                    __func__,
262                                                    g_strerror (errno));
263 #endif
264                                         g_strfreev (new_components);
265                                         g_strfreev (components);
266                                         return(NULL);
267                                 }
268                                 
269                                 entry = find_in_dir (current, components[0]);
270                                 if (entry == NULL) {
271                                         g_strfreev (new_components);
272                                         g_strfreev (components);
273                                         return(NULL);
274                                 }
275                                 
276                                 new_components[component++] = entry;
277                         }
278                 } else {
279                                 new_components[component++] = g_strdup (components[0]);
280                 }
281         }
282
283 #ifdef DEBUG
284         g_message ("%s: Got first entry: [%s]\n", __func__, new_components[0]);
285 #endif
286
287         g_assert (component == 1);
288         
289         for(; component < num_components; component++) {
290                 gchar *entry;
291                 gchar *path_so_far;
292                 
293                 if (!last_exists &&
294                     component == num_components -1) {
295                         entry = g_strdup (components[component]);
296                         closedir (scanning);
297                 } else {
298                         entry = find_in_dir (scanning, components[component]);
299                         if (entry == NULL) {
300                                 g_strfreev (new_components);
301                                 g_strfreev (components);
302                                 return(NULL);
303                         }
304                 }
305                 
306                 new_components[component] = entry;
307                 
308                 if (component < num_components -1) {
309                         path_so_far = g_strjoinv ("/", new_components);
310
311                         scanning = opendir (path_so_far);
312                         g_free (path_so_far);
313                         if (scanning == NULL) {
314                                 g_strfreev (new_components);
315                                 g_strfreev (components);
316                                 return(NULL);
317                         }
318                 }
319         }
320         
321         g_strfreev (components);
322
323         new_pathname = g_strjoinv ("/", new_components);
324
325 #ifdef DEBUG
326         g_message ("%s: pathname [%s] became [%s]\n", __func__, pathname,
327                    new_pathname);
328 #endif
329         
330         g_strfreev (new_components);
331
332         if ((last_exists &&
333              access (new_pathname, F_OK) == 0) ||
334             (!last_exists)) {
335                 return(new_pathname);
336         }
337         
338         g_free (new_pathname);
339         return(NULL);
340 }
341
342 int _wapi_open (const char *pathname, int flags, mode_t mode)
343 {
344         int fd;
345         gchar *located_filename;
346         
347         if (flags & O_CREAT) {
348                 located_filename = find_file (pathname, FALSE);
349                 if (located_filename == NULL) {
350                         fd = open (pathname, flags, mode);
351                 } else {
352                         fd = open (located_filename, flags, mode);
353                         g_free (located_filename);
354                 }
355         } else {
356                 fd = open (pathname, flags, mode);
357                 if (fd == -1 &&
358                     (errno == ENOENT || errno == ENOTDIR) &&
359                     portability_helpers > 0) {
360                         int saved_errno = errno;
361                         located_filename = find_file (pathname, TRUE);
362                         
363                         if (located_filename == NULL) {
364                                 errno = saved_errno;
365                                 return (-1);
366                         }
367                         
368                         fd = open (located_filename, flags, mode);
369                         g_free (located_filename);
370                 }
371         }
372         
373         
374         return(fd);
375 }
376
377 int _wapi_access (const char *pathname, int mode)
378 {
379         int ret;
380         
381         ret = access (pathname, mode);
382         if (ret == -1 &&
383             (errno == ENOENT || errno == ENOTDIR) &&
384             portability_helpers > 0) {
385                 int saved_errno = errno;
386                 gchar *located_filename = find_file (pathname, TRUE);
387                 
388                 if (located_filename == NULL) {
389                         errno = saved_errno;
390                         return(-1);
391                 }
392                 
393                 ret = access (located_filename, mode);
394                 g_free (located_filename);
395         }
396         
397         return(ret);
398 }
399
400 int _wapi_chmod (const char *pathname, mode_t mode)
401 {
402         int ret;
403         
404         ret = chmod (pathname, mode);
405         if (ret == -1 &&
406             (errno == ENOENT || errno == ENOTDIR) &&
407             portability_helpers > 0) {
408                 int saved_errno = errno;
409                 gchar *located_filename = find_file (pathname, TRUE);
410                 
411                 if (located_filename == NULL) {
412                         errno = saved_errno;
413                         return(-1);
414                 }
415                 
416                 ret = chmod (located_filename, mode);
417                 g_free (located_filename);
418         }
419         
420         return(ret);
421 }
422
423 int _wapi_utime (const char *filename, const struct utimbuf *buf)
424 {
425         int ret;
426         
427         ret = utime (filename, buf);
428         if (ret == -1 &&
429             errno == ENOENT &&
430             portability_helpers > 0) {
431                 int saved_errno = errno;
432                 gchar *located_filename = find_file (filename, TRUE);
433                 
434                 if (located_filename == NULL) {
435                         errno = saved_errno;
436                         return(-1);
437                 }
438                 
439                 ret = utime (located_filename, buf);
440                 g_free (located_filename);
441         }
442         
443         return(ret);
444 }
445
446 int _wapi_unlink (const char *pathname)
447 {
448         int ret;
449         
450         ret = unlink (pathname);
451         if (ret == -1 &&
452             (errno == ENOENT || errno == ENOTDIR || errno == EISDIR) &&
453             portability_helpers > 0) {
454                 int saved_errno = errno;
455                 gchar *located_filename = find_file (pathname, TRUE);
456                 
457                 if (located_filename == NULL) {
458                         errno = saved_errno;
459                         return(-1);
460                 }
461                 
462                 ret = unlink (located_filename);
463                 g_free (located_filename);
464         }
465         
466         return(ret);
467 }
468
469 int _wapi_rename (const char *oldpath, const char *newpath)
470 {
471         int ret;
472         gchar *located_newpath = find_file (newpath, FALSE);
473         
474         if (located_newpath == NULL) {
475                 ret = rename (oldpath, newpath);
476         } else {
477                 ret = rename (oldpath, located_newpath);
478         
479                 if (ret == -1 &&
480                     (errno == EISDIR || errno == ENAMETOOLONG ||
481                      errno == ENOENT || errno == ENOTDIR || errno == EXDEV) &&
482                     portability_helpers > 0) {
483                         int saved_errno = errno;
484                         gchar *located_oldpath = find_file (oldpath, TRUE);
485                         
486                         if (located_oldpath == NULL) {
487                                 g_free (located_oldpath);
488                                 g_free (located_newpath);
489                         
490                                 errno = saved_errno;
491                                 return(-1);
492                         }
493                         
494                         ret = rename (located_oldpath, located_newpath);
495                         g_free (located_oldpath);
496                 }
497                 g_free (located_newpath);
498         }
499         
500         return(ret);
501 }
502
503 int _wapi_stat (const char *path, struct stat *buf)
504 {
505         int ret;
506         
507         ret = stat (path, buf);
508         if (ret == -1 &&
509             (errno == ENOENT || errno == ENOTDIR) &&
510             portability_helpers > 0) {
511                 int saved_errno = errno;
512                 gchar *located_filename = find_file (path, TRUE);
513                 
514                 if (located_filename == NULL) {
515                         errno = saved_errno;
516                         return(-1);
517                 }
518                 
519                 ret = stat (located_filename, buf);
520                 g_free (located_filename);
521         }
522         
523         return(ret);
524 }
525
526 int _wapi_lstat (const char *path, struct stat *buf)
527 {
528         int ret;
529         
530         ret = lstat (path, buf);
531         if (ret == -1 &&
532             (errno == ENOENT || errno == ENOTDIR) &&
533             portability_helpers > 0) {
534                 int saved_errno = errno;
535                 gchar *located_filename = find_file (path, TRUE);
536                 
537                 if (located_filename == NULL) {
538                         errno = saved_errno;
539                         return(-1);
540                 }
541                 
542                 ret = lstat (located_filename, buf);
543                 g_free (located_filename);
544         }
545         
546         return(ret);
547 }
548
549 int _wapi_mkdir (const char *pathname, mode_t mode)
550 {
551         int ret;
552         gchar *located_filename = find_file (pathname, FALSE);
553         
554         if (located_filename == NULL) {
555                 ret = mkdir (pathname, mode);
556         } else {
557                 ret = mkdir (located_filename, mode);
558                 g_free (located_filename);
559         }
560         
561         return(ret);
562 }
563
564 int _wapi_rmdir (const char *pathname)
565 {
566         int ret;
567         
568         ret = rmdir (pathname);
569         if (ret == -1 &&
570             (errno == ENOENT || errno == ENOTDIR || errno == ENAMETOOLONG) &&
571             portability_helpers > 0) {
572                 int saved_errno = errno;
573                 gchar *located_filename = find_file (pathname, TRUE);
574                 
575                 if (located_filename == NULL) {
576                         errno = saved_errno;
577                         return(-1);
578                 }
579                 
580                 ret = rmdir (located_filename);
581                 g_free (located_filename);
582         }
583         
584         return(ret);
585 }
586
587 int _wapi_chdir (const char *path)
588 {
589         int ret;
590         
591         ret = chdir (path);
592         if (ret == -1 &&
593             (errno == ENOENT || errno == ENOTDIR || errno == ENAMETOOLONG) &&
594             portability_helpers > 0) {
595                 int saved_errno = errno;
596                 gchar *located_filename = find_file (path, TRUE);
597                 
598                 if (located_filename == NULL) {
599                         errno = saved_errno;
600                         return(-1);
601                 }
602                 
603                 ret = chdir (located_filename);
604                 g_free (located_filename);
605         }
606         
607         return(ret);
608 }
609
610 gchar *_wapi_basename (const gchar *filename)
611 {
612         gchar *new_filename = g_strdup (filename), *ret;
613
614         mono_once (&options_once, options_init);
615         
616         g_strdelimit (new_filename, "\\", '/');
617
618         if (portability_helpers & PORTABILITY_DRIVE &&
619             g_ascii_isalpha (new_filename[0]) &&
620             (new_filename[1] == ':')) {
621                 int len = strlen (new_filename);
622                 
623                 g_memmove (new_filename, new_filename + 2, len - 2);
624                 new_filename[len - 2] = '\0';
625         }
626         
627         ret = g_path_get_basename (new_filename);
628         g_free (new_filename);
629         
630         return(ret);
631 }
632
633 gchar *_wapi_dirname (const gchar *filename)
634 {
635         gchar *new_filename = g_strdup (filename), *ret;
636
637         mono_once (&options_once, options_init);
638         
639         g_strdelimit (new_filename, "\\", '/');
640
641         if (portability_helpers & PORTABILITY_DRIVE &&
642             g_ascii_isalpha (new_filename[0]) &&
643             (new_filename[1] == ':')) {
644                 int len = strlen (new_filename);
645                 
646                 g_memmove (new_filename, new_filename + 2, len - 2);
647                 new_filename[len - 2] = '\0';
648         }
649         
650         ret = g_path_get_dirname (new_filename);
651         g_free (new_filename);
652         
653         return(ret);
654 }
655
656 GDir *_wapi_g_dir_open (const gchar *path, guint flags, GError **error)
657 {
658         GDir *ret;
659         
660         ret = g_dir_open (path, flags, error);
661         if (ret == NULL &&
662             ((*error)->code == G_FILE_ERROR_NOENT ||
663              (*error)->code == G_FILE_ERROR_NOTDIR ||
664              (*error)->code == G_FILE_ERROR_NAMETOOLONG) &&
665             portability_helpers > 0) {
666                 gchar *located_filename = find_file (path, TRUE);
667                 GError *tmp_error;
668                 
669                 if (located_filename == NULL) {
670                         return(NULL);
671                 }
672                 
673                 ret = g_dir_open (located_filename, flags, &tmp_error);
674                 g_free (located_filename);
675                 if (tmp_error != NULL) {
676                         g_propagate_error (error, tmp_error);
677                 }
678         }
679         
680         return(ret);
681 }