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