Merge pull request #2394 from Mailaender/patch-1
[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 <sys/time.h>
22 #ifdef HAVE_DIRENT_H
23 # include <dirent.h>
24 #endif
25 #include <utime.h>
26 #include <sys/stat.h>
27
28 #include <mono/io-layer/error.h>
29 #include <mono/io-layer/wapi_glob.h>
30 #include <mono/io-layer/io-portability.h>
31 #include <mono/utils/mono-io-portability.h>
32
33 #undef DEBUG
34
35 int _wapi_open (const char *pathname, int flags, mode_t mode)
36 {
37         int fd;
38         gchar *located_filename;
39         
40         if (flags & O_CREAT) {
41                 located_filename = mono_portability_find_file (pathname, FALSE);
42                 if (located_filename == NULL) {
43                         fd = open (pathname, flags, mode);
44                 } else {
45                         fd = open (located_filename, flags, mode);
46                         g_free (located_filename);
47                 }
48         } else {
49                 fd = open (pathname, flags, mode);
50                 if (fd == -1 &&
51                     (errno == ENOENT || errno == ENOTDIR) &&
52                     IS_PORTABILITY_SET) {
53                         int saved_errno = errno;
54                         located_filename = mono_portability_find_file (pathname, TRUE);
55                         
56                         if (located_filename == NULL) {
57                                 errno = saved_errno;
58                                 return (-1);
59                         }
60                         
61                         fd = open (located_filename, flags, mode);
62                         g_free (located_filename);
63                 }
64         }
65         
66         
67         return(fd);
68 }
69
70 int _wapi_access (const char *pathname, int mode)
71 {
72         int ret;
73         
74         ret = access (pathname, mode);
75         if (ret == -1 &&
76             (errno == ENOENT || errno == ENOTDIR) &&
77             IS_PORTABILITY_SET) {
78                 int saved_errno = errno;
79                 gchar *located_filename = mono_portability_find_file (pathname, TRUE);
80                 
81                 if (located_filename == NULL) {
82                         errno = saved_errno;
83                         return(-1);
84                 }
85                 
86                 ret = access (located_filename, mode);
87                 g_free (located_filename);
88         }
89         
90         return(ret);
91 }
92
93 int _wapi_chmod (const char *pathname, mode_t mode)
94 {
95         int ret;
96         
97         ret = chmod (pathname, mode);
98         if (ret == -1 &&
99             (errno == ENOENT || errno == ENOTDIR) &&
100             IS_PORTABILITY_SET) {
101                 int saved_errno = errno;
102                 gchar *located_filename = mono_portability_find_file (pathname, TRUE);
103                 
104                 if (located_filename == NULL) {
105                         errno = saved_errno;
106                         return(-1);
107                 }
108                 
109                 ret = chmod (located_filename, mode);
110                 g_free (located_filename);
111         }
112         
113         return(ret);
114 }
115
116 int _wapi_utime (const char *filename, const struct utimbuf *buf)
117 {
118         int ret;
119         
120         ret = utime (filename, buf);
121         if (ret == -1 &&
122             errno == ENOENT &&
123             IS_PORTABILITY_SET) {
124                 int saved_errno = errno;
125                 gchar *located_filename = mono_portability_find_file (filename, TRUE);
126                 
127                 if (located_filename == NULL) {
128                         errno = saved_errno;
129                         return(-1);
130                 }
131                 
132                 ret = utime (located_filename, buf);
133                 g_free (located_filename);
134         }
135         
136         return(ret);
137 }
138
139 int _wapi_unlink (const char *pathname)
140 {
141         int ret;
142         
143         ret = unlink (pathname);
144         if (ret == -1 &&
145             (errno == ENOENT || errno == ENOTDIR || errno == EISDIR) &&
146             IS_PORTABILITY_SET) {
147                 int saved_errno = errno;
148                 gchar *located_filename = mono_portability_find_file (pathname, TRUE);
149                 
150                 if (located_filename == NULL) {
151                         errno = saved_errno;
152                         return(-1);
153                 }
154                 
155                 ret = unlink (located_filename);
156                 g_free (located_filename);
157         }
158         
159         return(ret);
160 }
161
162 int _wapi_rename (const char *oldpath, const char *newpath)
163 {
164         int ret;
165         gchar *located_newpath = mono_portability_find_file (newpath, FALSE);
166         
167         if (located_newpath == NULL) {
168                 ret = rename (oldpath, newpath);
169         } else {
170                 ret = rename (oldpath, located_newpath);
171         
172                 if (ret == -1 &&
173                     (errno == EISDIR || errno == ENAMETOOLONG ||
174                      errno == ENOENT || errno == ENOTDIR || errno == EXDEV) &&
175                     IS_PORTABILITY_SET) {
176                         int saved_errno = errno;
177                         gchar *located_oldpath = mono_portability_find_file (oldpath, TRUE);
178                         
179                         if (located_oldpath == NULL) {
180                                 g_free (located_oldpath);
181                                 g_free (located_newpath);
182                         
183                                 errno = saved_errno;
184                                 return(-1);
185                         }
186                         
187                         ret = rename (located_oldpath, located_newpath);
188                         g_free (located_oldpath);
189                 }
190                 g_free (located_newpath);
191         }
192         
193         return(ret);
194 }
195
196 int _wapi_stat (const char *path, struct stat *buf)
197 {
198         int ret;
199         
200         ret = stat (path, buf);
201         if (ret == -1 &&
202             (errno == ENOENT || errno == ENOTDIR) &&
203             IS_PORTABILITY_SET) {
204                 int saved_errno = errno;
205                 gchar *located_filename = mono_portability_find_file (path, TRUE);
206                 
207                 if (located_filename == NULL) {
208                         errno = saved_errno;
209                         return(-1);
210                 }
211                 
212                 ret = stat (located_filename, buf);
213                 g_free (located_filename);
214         }
215         
216         return(ret);
217 }
218
219 int _wapi_lstat (const char *path, struct stat *buf)
220 {
221         int ret;
222         
223         ret = lstat (path, buf);
224         if (ret == -1 &&
225             (errno == ENOENT || errno == ENOTDIR) &&
226             IS_PORTABILITY_SET) {
227                 int saved_errno = errno;
228                 gchar *located_filename = mono_portability_find_file (path, TRUE);
229                 
230                 if (located_filename == NULL) {
231                         errno = saved_errno;
232                         return(-1);
233                 }
234                 
235                 ret = lstat (located_filename, buf);
236                 g_free (located_filename);
237         }
238         
239         return(ret);
240 }
241
242 int _wapi_mkdir (const char *pathname, mode_t mode)
243 {
244         int ret;
245         gchar *located_filename = mono_portability_find_file (pathname, FALSE);
246         
247         if (located_filename == NULL) {
248                 ret = mkdir (pathname, mode);
249         } else {
250                 ret = mkdir (located_filename, mode);
251                 g_free (located_filename);
252         }
253         
254         return(ret);
255 }
256
257 int _wapi_rmdir (const char *pathname)
258 {
259         int ret;
260         
261         ret = rmdir (pathname);
262         if (ret == -1 &&
263             (errno == ENOENT || errno == ENOTDIR || errno == ENAMETOOLONG) &&
264             IS_PORTABILITY_SET) {
265                 int saved_errno = errno;
266                 gchar *located_filename = mono_portability_find_file (pathname, TRUE);
267                 
268                 if (located_filename == NULL) {
269                         errno = saved_errno;
270                         return(-1);
271                 }
272                 
273                 ret = rmdir (located_filename);
274                 g_free (located_filename);
275         }
276         
277         return(ret);
278 }
279
280 int _wapi_chdir (const char *path)
281 {
282         int ret;
283         
284         ret = chdir (path);
285         if (ret == -1 &&
286             (errno == ENOENT || errno == ENOTDIR || errno == ENAMETOOLONG) &&
287             IS_PORTABILITY_SET) {
288                 int saved_errno = errno;
289                 gchar *located_filename = mono_portability_find_file (path, TRUE);
290                 
291                 if (located_filename == NULL) {
292                         errno = saved_errno;
293                         return(-1);
294                 }
295                 
296                 ret = chdir (located_filename);
297                 g_free (located_filename);
298         }
299         
300         return(ret);
301 }
302
303 gchar *_wapi_basename (const gchar *filename)
304 {
305         gchar *new_filename = g_strdup (filename), *ret;
306
307         if (IS_PORTABILITY_SET) {
308                 g_strdelimit (new_filename, "\\", '/');
309         }
310
311         if (IS_PORTABILITY_DRIVE &&
312             g_ascii_isalpha (new_filename[0]) &&
313             (new_filename[1] == ':')) {
314                 int len = strlen (new_filename);
315                 
316                 g_memmove (new_filename, new_filename + 2, len - 2);
317                 new_filename[len - 2] = '\0';
318         }
319         
320         ret = g_path_get_basename (new_filename);
321         g_free (new_filename);
322         
323         return(ret);
324 }
325
326 gchar *_wapi_dirname (const gchar *filename)
327 {
328         gchar *new_filename = g_strdup (filename), *ret;
329
330         if (IS_PORTABILITY_SET) {
331                 g_strdelimit (new_filename, "\\", '/');
332         }
333
334         if (IS_PORTABILITY_DRIVE &&
335             g_ascii_isalpha (new_filename[0]) &&
336             (new_filename[1] == ':')) {
337                 int len = strlen (new_filename);
338                 
339                 g_memmove (new_filename, new_filename + 2, len - 2);
340                 new_filename[len - 2] = '\0';
341         }
342         
343         ret = g_path_get_dirname (new_filename);
344         g_free (new_filename);
345         
346         return(ret);
347 }
348
349 GDir *_wapi_g_dir_open (const gchar *path, guint flags, GError **error)
350 {
351         GDir *ret;
352         
353         ret = g_dir_open (path, flags, error);
354         if (ret == NULL &&
355             ((*error)->code == G_FILE_ERROR_NOENT ||
356              (*error)->code == G_FILE_ERROR_NOTDIR ||
357              (*error)->code == G_FILE_ERROR_NAMETOOLONG) &&
358             IS_PORTABILITY_SET) {
359                 gchar *located_filename = mono_portability_find_file (path, TRUE);
360                 GError *tmp_error = NULL;
361                 
362                 if (located_filename == NULL) {
363                         return(NULL);
364                 }
365                 
366                 ret = g_dir_open (located_filename, flags, &tmp_error);
367                 g_free (located_filename);
368                 if (tmp_error == NULL) {
369                         g_clear_error (error);
370                 }
371         }
372         
373         return(ret);
374 }
375
376
377 static gint
378 file_compare (gconstpointer a, gconstpointer b)
379 {
380         gchar *astr = *(gchar **) a;
381         gchar *bstr = *(gchar **) b;
382
383         return strcmp (astr, bstr);
384 }
385
386 static gint
387 get_errno_from_g_file_error (gint error)
388 {
389         switch (error) {
390 #ifdef EACCESS
391         case G_FILE_ERROR_ACCES:
392                 error = EACCES;
393                 break;
394 #endif
395 #ifdef ENAMETOOLONG
396         case G_FILE_ERROR_NAMETOOLONG:
397                 error = ENAMETOOLONG;
398                 break;
399 #endif
400 #ifdef ENOENT
401         case G_FILE_ERROR_NOENT:
402                 error = ENOENT;
403                 break;
404 #endif
405 #ifdef ENOTDIR
406         case G_FILE_ERROR_NOTDIR:
407                 error = ENOTDIR;
408                 break;
409 #endif
410 #ifdef ENXIO
411         case G_FILE_ERROR_NXIO:
412                 error = ENXIO;
413                 break;
414 #endif
415 #ifdef ENODEV
416         case G_FILE_ERROR_NODEV:
417                 error = ENODEV;
418                 break;
419 #endif
420 #ifdef EROFS
421         case G_FILE_ERROR_ROFS:
422                 error = EROFS;
423                 break;
424 #endif
425 #ifdef ETXTBSY
426         case G_FILE_ERROR_TXTBSY:
427                 error = ETXTBSY;
428                 break;
429 #endif
430 #ifdef EFAULT
431         case G_FILE_ERROR_FAULT:
432                 error = EFAULT;
433                 break;
434 #endif
435 #ifdef ELOOP
436         case G_FILE_ERROR_LOOP:
437                 error = ELOOP;
438                 break;
439 #endif
440 #ifdef ENOSPC
441         case G_FILE_ERROR_NOSPC:
442                 error = ENOSPC;
443                 break;
444 #endif
445 #ifdef ENOMEM
446         case G_FILE_ERROR_NOMEM:
447                 error = ENOMEM;
448                 break;
449 #endif
450 #ifdef EMFILE
451         case G_FILE_ERROR_MFILE:
452                 error = EMFILE;
453                 break;
454 #endif
455 #ifdef ENFILE
456         case G_FILE_ERROR_NFILE:
457                 error = ENFILE;
458                 break;
459 #endif
460 #ifdef EBADF
461         case G_FILE_ERROR_BADF:
462                 error = EBADF;
463                 break;
464 #endif
465 #ifdef EINVAL
466         case G_FILE_ERROR_INVAL:
467                 error = EINVAL;
468                 break;
469 #endif
470 #ifdef EPIPE
471         case G_FILE_ERROR_PIPE:
472                 error = EPIPE;
473                 break;
474 #endif
475 #ifdef EAGAIN
476         case G_FILE_ERROR_AGAIN:
477                 error = EAGAIN;
478                 break;
479 #endif
480 #ifdef EINTR
481         case G_FILE_ERROR_INTR:
482                 error = EINTR;
483                 break;
484 #endif
485 #ifdef EWIO
486         case G_FILE_ERROR_IO:
487                 error = EIO;
488                 break;
489 #endif
490 #ifdef EPERM
491         case G_FILE_ERROR_PERM:
492                 error = EPERM;
493                 break;
494 #endif
495         case G_FILE_ERROR_FAILED:
496                 error = ERROR_INVALID_PARAMETER;
497                 break;
498         }
499
500         return error;
501 }
502
503 /* scandir using glib */
504 gint _wapi_io_scandir (const gchar *dirname, const gchar *pattern,
505                        gchar ***namelist)
506 {
507         GError *error = NULL;
508         GDir *dir;
509         GPtrArray *names;
510         gint result;
511         wapi_glob_t glob_buf;
512         int flags = 0, i;
513         
514         dir = _wapi_g_dir_open (dirname, 0, &error);
515         if (dir == NULL) {
516                 /* g_dir_open returns ENOENT on directories on which we don't
517                  * have read/x permission */
518                 gint errnum = get_errno_from_g_file_error (error->code);
519                 g_error_free (error);
520                 if (errnum == ENOENT &&
521                     !_wapi_access (dirname, F_OK) &&
522                     _wapi_access (dirname, R_OK|X_OK)) {
523                         errnum = EACCES;
524                 }
525
526                 errno = errnum;
527                 return -1;
528         }
529
530         if (IS_PORTABILITY_CASE) {
531                 flags = WAPI_GLOB_IGNORECASE;
532         }
533         
534         result = _wapi_glob (dir, pattern, flags, &glob_buf);
535         if (g_str_has_suffix (pattern, ".*")) {
536                 /* Special-case the patterns ending in '.*', as
537                  * windows also matches entries with no extension with
538                  * this pattern.
539                  * 
540                  * TODO: should this be a MONO_IOMAP option?
541                  */
542                 gchar *pattern2 = g_strndup (pattern, strlen (pattern) - 2);
543                 gint result2;
544                 
545                 g_dir_rewind (dir);
546                 result2 = _wapi_glob (dir, pattern2, flags | WAPI_GLOB_APPEND | WAPI_GLOB_UNIQUE, &glob_buf);
547
548                 g_free (pattern2);
549
550                 if (result != 0) {
551                         result = result2;
552                 }
553         }
554         
555         g_dir_close (dir);
556         if (glob_buf.gl_pathc == 0) {
557                 return(0);
558         } else if (result != 0) {
559                 return(-1);
560         }
561         
562         names = g_ptr_array_new ();
563         for (i = 0; i < glob_buf.gl_pathc; i++) {
564                 g_ptr_array_add (names, g_strdup (glob_buf.gl_pathv[i]));
565         }
566
567         _wapi_globfree (&glob_buf);
568
569         result = names->len;
570         if (result > 0) {
571                 g_ptr_array_sort (names, file_compare);
572                 g_ptr_array_set_size (names, result + 1);
573
574                 *namelist = (gchar **) g_ptr_array_free (names, FALSE);
575         } else {
576                 g_ptr_array_free (names, TRUE);
577         }
578
579         return result;
580 }