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