Replace strcasecmp() with lstrcmpiA() to help compilation with Windows
[wine] / dlls / ntdll / directory.c
1 /*
2  * NTDLL directory functions
3  *
4  * Copyright 1993 Erik Bos
5  * Copyright 2003 Eric Pouech
6  * Copyright 1996, 2004 Alexandre Julliard
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
22
23 #include "config.h"
24 #include "wine/port.h"
25
26 #include <sys/types.h>
27 #include <dirent.h>
28 #include <errno.h>
29 #include <fcntl.h>
30 #include <stdarg.h>
31 #include <string.h>
32 #include <stdlib.h>
33 #include <stdio.h>
34 #ifdef HAVE_MNTENT_H
35 #include <mntent.h>
36 #endif
37 #include <sys/stat.h>
38 #ifdef HAVE_SYS_IOCTL_H
39 #include <sys/ioctl.h>
40 #endif
41 #ifdef HAVE_LINUX_IOCTL_H
42 #include <linux/ioctl.h>
43 #endif
44 #ifdef HAVE_SYS_PARAM_H
45 #include <sys/param.h>
46 #endif
47 #ifdef HAVE_SYS_MOUNT_H
48 #include <sys/mount.h>
49 #endif
50 #include <time.h>
51 #ifdef HAVE_UNISTD_H
52 # include <unistd.h>
53 #endif
54
55 #define NONAMELESSUNION
56 #define NONAMELESSSTRUCT
57 #include "windef.h"
58 #include "winbase.h"
59 #include "winnt.h"
60 #include "winreg.h"
61 #include "ntstatus.h"
62 #include "winternl.h"
63 #include "ntdll_misc.h"
64 #include "wine/unicode.h"
65 #include "wine/server.h"
66 #include "wine/library.h"
67 #include "wine/debug.h"
68
69 WINE_DEFAULT_DEBUG_CHANNEL(file);
70
71 /* Define the VFAT ioctl to get both short and long file names */
72 /* FIXME: is it possible to get this to work on other systems? */
73 #ifdef linux
74 /* We want the real kernel dirent structure, not the libc one */
75 typedef struct
76 {
77     long d_ino;
78     long d_off;
79     unsigned short d_reclen;
80     char d_name[256];
81 } KERNEL_DIRENT;
82
83 #define VFAT_IOCTL_READDIR_BOTH  _IOR('r', 1, KERNEL_DIRENT [2] )
84
85 #ifndef O_DIRECTORY
86 # define O_DIRECTORY 0200000 /* must be directory */
87 #endif
88
89 /* Using the same seekdir value across multiple directories is not portable,  */
90 /* but it works on Linux, and it's a major performance gain so we want to use */
91 /* it if possible. */
92 /* FIXME: do some sort of runtime check instead */
93 #define USE_SEEKDIR
94
95 #else   /* linux */
96 #undef VFAT_IOCTL_READDIR_BOTH  /* just in case... */
97 #undef USE_SEEKDIR
98 #endif  /* linux */
99
100 #define IS_OPTION_TRUE(ch) ((ch) == 'y' || (ch) == 'Y' || (ch) == 't' || (ch) == 'T' || (ch) == '1')
101 #define IS_SEPARATOR(ch)   ((ch) == '\\' || (ch) == '/')
102
103 #define INVALID_NT_CHARS   '*','?','<','>','|','"'
104 #define INVALID_DOS_CHARS  INVALID_NT_CHARS,'+','=',',',';','[',']',' ','\345'
105
106 #define MAX_DIR_ENTRY_LEN 255  /* max length of a directory entry in chars */
107
108 static int show_dir_symlinks = -1;
109 static int show_dot_files;
110
111 /* at some point we may want to allow Winelib apps to set this */
112 static const int is_case_sensitive = FALSE;
113
114 static CRITICAL_SECTION dir_section;
115 static CRITICAL_SECTION_DEBUG critsect_debug =
116 {
117     0, 0, &dir_section,
118     { &critsect_debug.ProcessLocksList, &critsect_debug.ProcessLocksList },
119       0, 0, { 0, (DWORD)(__FILE__ ": dir_section") }
120 };
121 static CRITICAL_SECTION dir_section = { &critsect_debug, -1, 0, 0, 0, 0 };
122
123
124 /***********************************************************************
125  *           seekdir_wrapper
126  *
127  * Wrapper for supporting seekdir across multiple directory objects.
128  */
129 static inline void seekdir_wrapper( DIR *dir, off_t pos )
130 {
131 #ifdef USE_SEEKDIR
132     seekdir( dir, pos );
133 #else
134     while (pos-- > 0) if (!readdir( dir )) break;
135 #endif
136 }
137
138 /***********************************************************************
139  *           telldir_wrapper
140  *
141  * Wrapper for supporting telldir across multiple directory objects.
142  */
143 static inline off_t telldir_wrapper( DIR *dir, off_t pos, int count )
144 {
145 #ifdef USE_SEEKDIR
146     return telldir( dir );
147 #else
148     return pos + count;
149 #endif
150 }
151
152
153 /***********************************************************************
154  *           get_default_com_device
155  *
156  * Return the default device to use for serial ports.
157  */
158 static char *get_default_com_device( int num )
159 {
160     char *ret = NULL;
161
162     if (!num || num > 9) return ret;
163 #ifdef linux
164     ret = RtlAllocateHeap( GetProcessHeap(), 0, sizeof("/dev/ttyS0") );
165     if (ret)
166     {
167         strcpy( ret, "/dev/ttyS0" );
168         ret[strlen(ret) - 1] = '0' + num - 1;
169     }
170 #else
171     FIXME( "no known default for device com%d\n", num );
172 #endif
173     return ret;
174 }
175
176
177 /***********************************************************************
178  *           get_default_lpt_device
179  *
180  * Return the default device to use for parallel ports.
181  */
182 static char *get_default_lpt_device( int num )
183 {
184     char *ret = NULL;
185
186     if (!num || num > 9) return ret;
187 #ifdef linux
188     ret = RtlAllocateHeap( GetProcessHeap(), 0, sizeof("/dev/lp0") );
189     if (ret)
190     {
191         strcpy( ret, "/dev/lp0" );
192         ret[strlen(ret) - 1] = '0' + num - 1;
193     }
194 #else
195     FIXME( "no known default for device lpt%d\n", num );
196 #endif
197     return ret;
198 }
199
200
201 /***********************************************************************
202  *           parse_mount_entries
203  *
204  * Parse mount entries looking for a given device. Helper for get_default_drive_device.
205  */
206 #ifdef linux
207 static char *parse_mount_entries( FILE *f, dev_t dev, ino_t ino )
208 {
209     struct mntent *entry;
210     struct stat st;
211     char *device;
212
213     while ((entry = getmntent( f )))
214     {
215         if (stat( entry->mnt_dir, &st ) == -1) continue;
216         if (st.st_dev != dev || st.st_ino != ino) continue;
217         if (!strcmp( entry->mnt_type, "supermount" ))
218         {
219             if ((device = strstr( entry->mnt_opts, "dev=" )))
220             {
221                 char *p = strchr( device + 4, ',' );
222                 if (p) *p = 0;
223                 return device + 4;
224             }
225         }
226         else
227             return entry->mnt_fsname;
228     }
229     return NULL;
230 }
231 #endif
232
233 /***********************************************************************
234  *           get_default_drive_device
235  *
236  * Return the default device to use for a given drive mount point.
237  */
238 static char *get_default_drive_device( const char *root )
239 {
240     char *ret = NULL;
241
242 #ifdef linux
243     FILE *f;
244     char *device = NULL;
245     int fd, res = -1;
246     struct stat st;
247
248     /* try to open it first to force it to get mounted */
249     if ((fd = open( root, O_RDONLY | O_DIRECTORY )) != -1)
250     {
251         res = fstat( fd, &st );
252         close( fd );
253     }
254     /* now try normal stat just in case */
255     if (res == -1) res = stat( root, &st );
256     if (res == -1) return NULL;
257
258     RtlEnterCriticalSection( &dir_section );
259
260     if ((f = fopen( "/etc/mtab", "r" )))
261     {
262         device = parse_mount_entries( f, st.st_dev, st.st_ino );
263         endmntent( f );
264     }
265     /* look through fstab too in case it's not mounted (for instance if it's an audio CD) */
266     if (!device && (f = fopen( "/etc/fstab", "r" )))
267     {
268         device = parse_mount_entries( f, st.st_dev, st.st_ino );
269         endmntent( f );
270     }
271     if (device)
272     {
273         ret = RtlAllocateHeap( GetProcessHeap(), 0, strlen(device) + 1 );
274         if (ret) strcpy( ret, device );
275     }
276     RtlLeaveCriticalSection( &dir_section );
277 #elif defined(__APPLE__)
278     struct statfs *mntStat;
279     struct stat st;
280     int i;
281     int mntSize;
282     dev_t dev;
283     ino_t ino;
284     static const char path_bsd_device[] = "/dev/disk";
285     int res;
286
287     res = stat( root, &st );
288     if (res == -1) return NULL;
289
290     dev = st.st_dev;
291     ino = st.st_ino;
292
293     RtlEnterCriticalSection( &dir_section );
294
295     mntSize = getmntinfo(&mntStat, MNT_NOWAIT);
296
297     for (i = 0; i < mntSize && !ret; i++)
298     {
299         if (stat(mntStat[i].f_mntonname, &st ) == -1) continue;
300         if (st.st_dev != dev || st.st_ino != ino) continue;
301
302         /* FIXME add support for mounted network drive */
303         if ( strncmp(mntStat[i].f_mntfromname, path_bsd_device, strlen(path_bsd_device)) == 0)
304         {
305             /* set return value to the corresponding raw BSD node */
306             ret = RtlAllocateHeap( GetProcessHeap(), 0, strlen(mntStat[i].f_mntfromname) + 2 /* 2 : r and \0 */ );
307             if (ret)
308             {
309                 strcpy(ret, "/dev/r");
310                 strcat(ret, mntStat[i].f_mntfromname+sizeof("/dev/")-1);
311             }
312         }
313     }
314     RtlLeaveCriticalSection( &dir_section );
315 #else
316     static int warned;
317     if (!warned++) FIXME( "auto detection of DOS devices not supported on this platform\n" );
318 #endif
319     return ret;
320 }
321
322
323 /***********************************************************************
324  *           init_options
325  *
326  * Initialize the show_dir_symlinks and show_dot_files options.
327  */
328 static void init_options(void)
329 {
330     static const WCHAR WineW[] = {'M','a','c','h','i','n','e','\\',
331                                   'S','o','f','t','w','a','r','e','\\',
332                                   'W','i','n','e','\\','W','i','n','e','\\',
333                                   'C','o','n','f','i','g','\\','W','i','n','e',0};
334     static const WCHAR ShowDotFilesW[] = {'S','h','o','w','D','o','t','F','i','l','e','s',0};
335     static const WCHAR ShowDirSymlinksW[] = {'S','h','o','w','D','i','r','S','y','m','l','i','n','k','s',0};
336     char tmp[80];
337     HKEY hkey;
338     DWORD dummy;
339     OBJECT_ATTRIBUTES attr;
340     UNICODE_STRING nameW;
341
342     show_dot_files = show_dir_symlinks = 0;
343
344     attr.Length = sizeof(attr);
345     attr.RootDirectory = 0;
346     attr.ObjectName = &nameW;
347     attr.Attributes = 0;
348     attr.SecurityDescriptor = NULL;
349     attr.SecurityQualityOfService = NULL;
350     RtlInitUnicodeString( &nameW, WineW );
351
352     if (!NtOpenKey( &hkey, KEY_ALL_ACCESS, &attr ))
353     {
354         RtlInitUnicodeString( &nameW, ShowDotFilesW );
355         if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
356         {
357             WCHAR *str = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
358             show_dot_files = IS_OPTION_TRUE( str[0] );
359         }
360         RtlInitUnicodeString( &nameW, ShowDirSymlinksW );
361         if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
362         {
363             WCHAR *str = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
364             show_dir_symlinks = IS_OPTION_TRUE( str[0] );
365         }
366         NtClose( hkey );
367     }
368 }
369
370
371 /***********************************************************************
372  *           DIR_is_hidden_file
373  *
374  * Check if the specified file should be hidden based on its name and the show dot files option.
375  */
376 BOOL DIR_is_hidden_file( const UNICODE_STRING *name )
377 {
378     WCHAR *p, *end;
379
380     if (show_dir_symlinks == -1) init_options();
381     if (show_dot_files) return FALSE;
382
383     end = p = name->Buffer + name->Length/sizeof(WCHAR);
384     while (p > name->Buffer && IS_SEPARATOR(p[-1])) p--;
385     while (p > name->Buffer && !IS_SEPARATOR(p[-1])) p--;
386     if (p == end || *p != '.') return FALSE;
387     /* make sure it isn't '.' or '..' */
388     if (p + 1 == end) return FALSE;
389     if (p[1] == '.' && p + 2 == end) return FALSE;
390     return TRUE;
391 }
392
393
394 /***********************************************************************
395  *           hash_short_file_name
396  *
397  * Transform a Unix file name into a hashed DOS name. If the name is a valid
398  * DOS name, it is converted to upper-case; otherwise it is replaced by a
399  * hashed version that fits in 8.3 format.
400  * 'buffer' must be at least 12 characters long.
401  * Returns length of short name in bytes; short name is NOT null-terminated.
402  */
403 static ULONG hash_short_file_name( const UNICODE_STRING *name, LPWSTR buffer )
404 {
405     static const WCHAR invalid_chars[] = { INVALID_DOS_CHARS,'~','.',0 };
406     static const char hash_chars[32] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345";
407
408     LPCWSTR p, ext, end = name->Buffer + name->Length / sizeof(WCHAR);
409     LPWSTR dst;
410     unsigned short hash;
411     int i;
412
413     /* Compute the hash code of the file name */
414     /* If you know something about hash functions, feel free to */
415     /* insert a better algorithm here... */
416     if (!is_case_sensitive)
417     {
418         for (p = name->Buffer, hash = 0xbeef; p < end - 1; p++)
419             hash = (hash<<3) ^ (hash>>5) ^ tolowerW(*p) ^ (tolowerW(p[1]) << 8);
420         hash = (hash<<3) ^ (hash>>5) ^ tolowerW(*p); /* Last character */
421     }
422     else
423     {
424         for (p = name->Buffer, hash = 0xbeef; p < end - 1; p++)
425             hash = (hash << 3) ^ (hash >> 5) ^ *p ^ (p[1] << 8);
426         hash = (hash << 3) ^ (hash >> 5) ^ *p;  /* Last character */
427     }
428
429     /* Find last dot for start of the extension */
430     for (p = name->Buffer + 1, ext = NULL; p < end - 1; p++) if (*p == '.') ext = p;
431
432     /* Copy first 4 chars, replacing invalid chars with '_' */
433     for (i = 4, p = name->Buffer, dst = buffer; i > 0; i--, p++)
434     {
435         if (p == end || p == ext) break;
436         *dst++ = strchrW( invalid_chars, *p ) ? '_' : toupperW(*p);
437     }
438     /* Pad to 5 chars with '~' */
439     while (i-- >= 0) *dst++ = '~';
440
441     /* Insert hash code converted to 3 ASCII chars */
442     *dst++ = hash_chars[(hash >> 10) & 0x1f];
443     *dst++ = hash_chars[(hash >> 5) & 0x1f];
444     *dst++ = hash_chars[hash & 0x1f];
445
446     /* Copy the first 3 chars of the extension (if any) */
447     if (ext)
448     {
449         *dst++ = '.';
450         for (i = 3, ext++; (i > 0) && ext < end; i--, ext++)
451             *dst++ = strchrW( invalid_chars, *ext ) ? '_' : toupperW(*ext);
452     }
453     return dst - buffer;
454 }
455
456
457 /***********************************************************************
458  *           match_filename
459  *
460  * Check a long file name against a mask.
461  *
462  * Tests (done in W95 DOS shell - case insensitive):
463  * *.txt                        test1.test.txt                          *
464  * *st1*                        test1.txt                               *
465  * *.t??????.t*                 test1.ta.tornado.txt                    *
466  * *tornado*                    test1.ta.tornado.txt                    *
467  * t*t                          test1.ta.tornado.txt                    *
468  * ?est*                        test1.txt                               *
469  * ?est???                      test1.txt                               -
470  * *test1.txt*                  test1.txt                               *
471  * h?l?o*t.dat                  hellothisisatest.dat                    *
472  */
473 static BOOLEAN match_filename( const UNICODE_STRING *name_str, const UNICODE_STRING *mask_str )
474 {
475     int mismatch;
476     const WCHAR *name = name_str->Buffer;
477     const WCHAR *mask = mask_str->Buffer;
478     const WCHAR *name_end = name + name_str->Length / sizeof(WCHAR);
479     const WCHAR *mask_end = mask + mask_str->Length / sizeof(WCHAR);
480     const WCHAR *lastjoker = NULL;
481     const WCHAR *next_to_retry = NULL;
482
483     TRACE("(%s, %s)\n", debugstr_us(name_str), debugstr_us(mask_str));
484
485     while (name < name_end && mask < mask_end)
486     {
487         switch(*mask)
488         {
489         case '*':
490             mask++;
491             while (mask < mask_end && *mask == '*') mask++;  /* Skip consecutive '*' */
492             if (mask == mask_end) return TRUE; /* end of mask is all '*', so match */
493             lastjoker = mask;
494
495             /* skip to the next match after the joker(s) */
496             if (is_case_sensitive)
497                 while (name < name_end && (*name != *mask)) name++;
498             else
499                 while (name < name_end && (toupperW(*name) != toupperW(*mask))) name++;
500             next_to_retry = name;
501             break;
502         case '?':
503             mask++;
504             name++;
505             break;
506         default:
507             if (is_case_sensitive) mismatch = (*mask != *name);
508             else mismatch = (toupperW(*mask) != toupperW(*name));
509
510             if (!mismatch)
511             {
512                 mask++;
513                 name++;
514                 if (mask == mask_end)
515                 {
516                     if (name == name_end) return TRUE;
517                     if (lastjoker) mask = lastjoker;
518                 }
519             }
520             else /* mismatch ! */
521             {
522                 if (lastjoker) /* we had an '*', so we can try unlimitedly */
523                 {
524                     mask = lastjoker;
525
526                     /* this scan sequence was a mismatch, so restart
527                      * 1 char after the first char we checked last time */
528                     next_to_retry++;
529                     name = next_to_retry;
530                 }
531                 else return FALSE; /* bad luck */
532             }
533             break;
534         }
535     }
536     while (mask < mask_end && ((*mask == '.') || (*mask == '*')))
537         mask++;  /* Ignore trailing '.' or '*' in mask */
538     return (name == name_end && mask == mask_end);
539 }
540
541
542 /***********************************************************************
543  *           append_entry
544  *
545  * helper for NtQueryDirectoryFile
546  */
547 static FILE_BOTH_DIR_INFORMATION *append_entry( void *info_ptr, ULONG *pos, ULONG max_length,
548                                                 const char *long_name, const char *short_name,
549                                                 const UNICODE_STRING *mask )
550 {
551     FILE_BOTH_DIR_INFORMATION *info;
552     int i, long_len, short_len, total_len;
553     struct stat st;
554     WCHAR long_nameW[MAX_DIR_ENTRY_LEN];
555     WCHAR short_nameW[12];
556     UNICODE_STRING str;
557
558     long_len = ntdll_umbstowcs( 0, long_name, strlen(long_name), long_nameW, MAX_DIR_ENTRY_LEN );
559     if (long_len == -1) return NULL;
560
561     str.Buffer = long_nameW;
562     str.Length = long_len * sizeof(WCHAR);
563     str.MaximumLength = sizeof(long_nameW);
564
565     if (short_name)
566     {
567         short_len = ntdll_umbstowcs( 0, short_name, strlen(short_name),
568                                      short_nameW, sizeof(short_nameW) / sizeof(WCHAR) );
569         if (short_len == -1) short_len = sizeof(short_nameW) / sizeof(WCHAR);
570     }
571     else  /* generate a short name if necessary */
572     {
573         BOOLEAN spaces;
574
575         short_len = 0;
576         if (!RtlIsNameLegalDOS8Dot3( &str, NULL, &spaces ) || spaces)
577             short_len = hash_short_file_name( &str, short_nameW );
578     }
579
580     TRACE( "long %s short %s mask %s\n",
581            debugstr_us(&str), debugstr_wn(short_nameW, short_len), debugstr_us(mask) );
582
583     if (mask && !match_filename( &str, mask ))
584     {
585         if (!short_len) return NULL;  /* no short name to match */
586         str.Buffer = short_nameW;
587         str.Length = short_len * sizeof(WCHAR);
588         str.MaximumLength = sizeof(short_nameW);
589         if (!match_filename( &str, mask )) return NULL;
590     }
591
592     total_len = (sizeof(*info) - sizeof(info->FileName) + long_len*sizeof(WCHAR) + 3) & ~3;
593     info = (FILE_BOTH_DIR_INFORMATION *)((char *)info_ptr + *pos);
594
595     if (*pos + total_len > max_length) total_len = max_length - *pos;
596
597     if (lstat( long_name, &st ) == -1) return NULL;
598     if (S_ISLNK( st.st_mode ))
599     {
600         if (stat( long_name, &st ) == -1) return NULL;
601         if (S_ISDIR( st.st_mode ) && !show_dir_symlinks) return NULL;
602     }
603
604     info->NextEntryOffset = total_len;
605     info->FileIndex = 0;  /* NTFS always has 0 here, so let's not bother with it */
606
607     RtlSecondsSince1970ToTime( st.st_mtime, &info->CreationTime );
608     RtlSecondsSince1970ToTime( st.st_mtime, &info->LastWriteTime );
609     RtlSecondsSince1970ToTime( st.st_atime, &info->LastAccessTime );
610     RtlSecondsSince1970ToTime( st.st_ctime, &info->ChangeTime );
611
612     if (S_ISDIR(st.st_mode))
613     {
614         info->EndOfFile.QuadPart = info->AllocationSize.QuadPart = 0;
615         info->FileAttributes = FILE_ATTRIBUTE_DIRECTORY;
616     }
617     else
618     {
619         info->EndOfFile.QuadPart = st.st_size;
620         info->AllocationSize.QuadPart = (ULONGLONG)st.st_blocks * 512;
621         info->FileAttributes = FILE_ATTRIBUTE_ARCHIVE;
622     }
623
624     if (!(st.st_mode & S_IWUSR))
625         info->FileAttributes |= FILE_ATTRIBUTE_READONLY;
626
627     if (!show_dot_files && long_name[0] == '.' && long_name[1] && (long_name[1] != '.' || long_name[2]))
628         info->FileAttributes |= FILE_ATTRIBUTE_HIDDEN;
629
630     info->EaSize = 0; /* FIXME */
631     info->ShortNameLength = short_len * sizeof(WCHAR);
632     for (i = 0; i < short_len; i++) info->ShortName[i] = toupperW(short_nameW[i]);
633     info->FileNameLength = long_len * sizeof(WCHAR);
634     memcpy( info->FileName, long_nameW,
635             min( info->FileNameLength, total_len-sizeof(*info)+sizeof(info->FileName) ));
636
637     *pos += total_len;
638     return info;
639 }
640
641
642 /******************************************************************************
643  *  NtQueryDirectoryFile        [NTDLL.@]
644  *  ZwQueryDirectoryFile        [NTDLL.@]
645  */
646 NTSTATUS WINAPI NtQueryDirectoryFile( HANDLE handle, HANDLE event,
647                                       PIO_APC_ROUTINE apc_routine, PVOID apc_context,
648                                       PIO_STATUS_BLOCK io,
649                                       PVOID buffer, ULONG length,
650                                       FILE_INFORMATION_CLASS info_class,
651                                       BOOLEAN single_entry,
652                                       PUNICODE_STRING mask,
653                                       BOOLEAN restart_scan )
654 {
655     int cwd, fd;
656     FILE_BOTH_DIR_INFORMATION *info, *last_info = NULL;
657     static const int max_dir_info_size = sizeof(*info) + (MAX_DIR_ENTRY_LEN-1) * sizeof(WCHAR);
658
659     TRACE("(%p %p %p %p %p %p 0x%08lx 0x%08x 0x%08x %s 0x%08x\n",
660           handle, event, apc_routine, apc_context, io, buffer,
661           length, info_class, single_entry, debugstr_us(mask),
662           restart_scan);
663
664     if (length < sizeof(*info)) return STATUS_INFO_LENGTH_MISMATCH;
665
666     if (event || apc_routine)
667     {
668         FIXME( "Unsupported yet option\n" );
669         return io->u.Status = STATUS_NOT_IMPLEMENTED;
670     }
671     if (info_class != FileBothDirectoryInformation)
672     {
673         FIXME( "Unsupported file info class %d\n", info_class );
674         return io->u.Status = STATUS_NOT_IMPLEMENTED;
675     }
676
677     if ((io->u.Status = wine_server_handle_to_fd( handle, GENERIC_READ,
678                                                   &fd, NULL, NULL )) != STATUS_SUCCESS)
679         return io->u.Status;
680
681     io->Information = 0;
682
683     RtlEnterCriticalSection( &dir_section );
684
685     if (show_dir_symlinks == -1) init_options();
686
687     if ((cwd = open(".", O_RDONLY)) != -1 && fchdir( fd ) != -1)
688     {
689         off_t old_pos = 0;
690
691 #ifdef VFAT_IOCTL_READDIR_BOTH
692         KERNEL_DIRENT de[2];
693
694         io->u.Status = STATUS_SUCCESS;
695
696         /* Check if the VFAT ioctl is supported on this directory */
697
698         if (restart_scan) lseek( fd, 0, SEEK_SET );
699         else old_pos = lseek( fd, 0, SEEK_CUR );
700
701         if (ioctl( fd, VFAT_IOCTL_READDIR_BOTH, (long)de ) != -1)
702         {
703             if (length < max_dir_info_size)  /* we may have to return a partial entry here */
704             {
705                 for (;;)
706                 {
707                     if (!de[0].d_reclen) break;
708                     if (de[1].d_name[0])
709                         info = append_entry( buffer, &io->Information, length,
710                                              de[1].d_name, de[0].d_name, mask );
711                     else
712                         info = append_entry( buffer, &io->Information, length,
713                                              de[0].d_name, NULL, mask );
714                     if (info)
715                     {
716                         last_info = info;
717                         if ((char *)info->FileName + info->FileNameLength > (char *)buffer + length)
718                         {
719                             io->u.Status = STATUS_BUFFER_OVERFLOW;
720                             lseek( fd, old_pos, SEEK_SET );  /* restore pos to previous entry */
721                         }
722                         break;
723                     }
724                     old_pos = lseek( fd, 0, SEEK_CUR );
725                     if (ioctl( fd, VFAT_IOCTL_READDIR_BOTH, (long)de ) == -1) break;
726                 }
727             }
728             else  /* we'll only return full entries, no need to worry about overflow */
729             {
730                 for (;;)
731                 {
732                     if (!de[0].d_reclen) break;
733                     if (de[1].d_name[0])
734                         info = append_entry( buffer, &io->Information, length,
735                                              de[1].d_name, de[0].d_name, mask );
736                     else
737                         info = append_entry( buffer, &io->Information, length,
738                                              de[0].d_name, NULL, mask );
739                     if (info)
740                     {
741                         last_info = info;
742                         if (single_entry) break;
743                         /* check if we still have enough space for the largest possible entry */
744                         if (io->Information + max_dir_info_size > length) break;
745                     }
746                     if (ioctl( fd, VFAT_IOCTL_READDIR_BOTH, (long)de ) == -1) break;
747                 }
748             }
749         }
750         else if (errno != ENOENT)
751 #endif  /* VFAT_IOCTL_READDIR_BOTH */
752         {
753             DIR *dir;
754             struct dirent *de;
755
756             if (!(dir = opendir( "." )))
757             {
758                 io->u.Status = FILE_GetNtStatus();
759                 goto done;
760             }
761             if (!restart_scan)
762             {
763                 old_pos = lseek( fd, 0, SEEK_CUR );
764                 seekdir_wrapper( dir, old_pos );
765             }
766             io->u.Status = STATUS_SUCCESS;
767
768             if (length < max_dir_info_size)  /* we may have to return a partial entry here */
769             {
770                 while ((de = readdir( dir )))
771                 {
772                     info = append_entry( buffer, &io->Information, length,
773                                          de->d_name, NULL, mask );
774                     if (info)
775                     {
776                         last_info = info;
777                         if ((char *)info->FileName + info->FileNameLength > (char *)buffer + length)
778                             io->u.Status = STATUS_BUFFER_OVERFLOW;
779                         else
780                             old_pos = telldir_wrapper( dir, old_pos, 1 );
781                         break;
782                     }
783                     old_pos = telldir_wrapper( dir, old_pos, 1 );
784                 }
785             }
786             else  /* we'll only return full entries, no need to worry about overflow */
787             {
788                 int count = 0;
789                 while ((de = readdir( dir )))
790                 {
791                     count++;
792                     info = append_entry( buffer, &io->Information, length,
793                                          de->d_name, NULL, mask );
794                     if (info)
795                     {
796                         last_info = info;
797                         if (single_entry) break;
798                         /* check if we still have enough space for the largest possible entry */
799                         if (io->Information + max_dir_info_size > length) break;
800                     }
801                 }
802                 old_pos = telldir_wrapper( dir, old_pos, count );
803             }
804             lseek( fd, old_pos, SEEK_SET );  /* store dir offset as filepos for fd */
805             closedir( dir );
806         }
807
808         if (last_info) last_info->NextEntryOffset = 0;
809         else io->u.Status = restart_scan ? STATUS_NO_SUCH_FILE : STATUS_NO_MORE_FILES;
810
811     done:
812         if (fchdir( cwd ) == -1) chdir( "/" );
813     }
814     else io->u.Status = FILE_GetNtStatus();
815
816     RtlLeaveCriticalSection( &dir_section );
817
818     wine_server_release_fd( handle, fd );
819     if (cwd != -1) close( cwd );
820     TRACE( "=> %lx (%ld)\n", io->u.Status, io->Information );
821     return io->u.Status;
822 }
823
824
825 /***********************************************************************
826  *           find_file_in_dir
827  *
828  * Find a file in a directory the hard way, by doing a case-insensitive search.
829  * The file found is appended to unix_name at pos.
830  * There must be at least MAX_DIR_ENTRY_LEN+2 chars available at pos.
831  */
832 static NTSTATUS find_file_in_dir( char *unix_name, int pos, const WCHAR *name, int length,
833                                   int check_case )
834 {
835     WCHAR buffer[MAX_DIR_ENTRY_LEN];
836     UNICODE_STRING str;
837     BOOLEAN spaces;
838     DIR *dir;
839     struct dirent *de;
840     struct stat st;
841     int ret, used_default, is_name_8_dot_3;
842
843     /* try a shortcut for this directory */
844
845     unix_name[pos++] = '/';
846     ret = ntdll_wcstoumbs( 0, name, length, unix_name + pos, MAX_DIR_ENTRY_LEN,
847                            NULL, &used_default );
848     /* if we used the default char, the Unix name won't round trip properly back to Unicode */
849     /* so it cannot match the file we are looking for */
850     if (ret >= 0 && !used_default)
851     {
852         unix_name[pos + ret] = 0;
853         if (!stat( unix_name, &st )) return STATUS_SUCCESS;
854     }
855     if (check_case) goto not_found;  /* we want an exact match */
856
857     if (pos > 1) unix_name[pos - 1] = 0;
858     else unix_name[1] = 0;  /* keep the initial slash */
859
860     /* check if it fits in 8.3 so that we don't look for short names if we won't need them */
861
862     str.Buffer = (WCHAR *)name;
863     str.Length = length * sizeof(WCHAR);
864     str.MaximumLength = str.Length;
865     is_name_8_dot_3 = RtlIsNameLegalDOS8Dot3( &str, NULL, &spaces ) && !spaces;
866
867     /* now look for it through the directory */
868
869 #ifdef VFAT_IOCTL_READDIR_BOTH
870     if (is_name_8_dot_3)
871     {
872         int fd = open( unix_name, O_RDONLY | O_DIRECTORY );
873         if (fd != -1)
874         {
875             KERNEL_DIRENT de[2];
876
877             if (ioctl( fd, VFAT_IOCTL_READDIR_BOTH, (long)de ) != -1)
878             {
879                 unix_name[pos - 1] = '/';
880                 for (;;)
881                 {
882                     if (!de[0].d_reclen) break;
883
884                     if (de[1].d_name[0])
885                     {
886                         ret = ntdll_umbstowcs( 0, de[1].d_name, strlen(de[1].d_name),
887                                                buffer, MAX_DIR_ENTRY_LEN );
888                         if (ret == length && !memicmpW( buffer, name, length))
889                         {
890                             strcpy( unix_name + pos, de[1].d_name );
891                             close( fd );
892                             return STATUS_SUCCESS;
893                         }
894                     }
895                     ret = ntdll_umbstowcs( 0, de[0].d_name, strlen(de[0].d_name),
896                                            buffer, MAX_DIR_ENTRY_LEN );
897                     if (ret == length && !memicmpW( buffer, name, length))
898                     {
899                         strcpy( unix_name + pos,
900                                 de[1].d_name[0] ? de[1].d_name : de[0].d_name );
901                         close( fd );
902                         return STATUS_SUCCESS;
903                     }
904                     if (ioctl( fd, VFAT_IOCTL_READDIR_BOTH, (long)de ) == -1)
905                     {
906                         close( fd );
907                         goto not_found;
908                     }
909                 }
910             }
911             close( fd );
912         }
913         /* fall through to normal handling */
914     }
915 #endif /* VFAT_IOCTL_READDIR_BOTH */
916
917     if (!(dir = opendir( unix_name )))
918     {
919         if (errno == ENOENT) return STATUS_OBJECT_PATH_NOT_FOUND;
920         else return FILE_GetNtStatus();
921     }
922     unix_name[pos - 1] = '/';
923     str.Buffer = buffer;
924     str.MaximumLength = sizeof(buffer);
925     while ((de = readdir( dir )))
926     {
927         ret = ntdll_umbstowcs( 0, de->d_name, strlen(de->d_name), buffer, MAX_DIR_ENTRY_LEN );
928         if (ret == length && !memicmpW( buffer, name, length ))
929         {
930             strcpy( unix_name + pos, de->d_name );
931             closedir( dir );
932             return STATUS_SUCCESS;
933         }
934
935         if (!is_name_8_dot_3) continue;
936
937         str.Length = ret * sizeof(WCHAR);
938         if (!RtlIsNameLegalDOS8Dot3( &str, NULL, &spaces ) || spaces)
939         {
940             WCHAR short_nameW[12];
941             ret = hash_short_file_name( &str, short_nameW );
942             if (ret == length && !memicmpW( short_nameW, name, length ))
943             {
944                 strcpy( unix_name + pos, de->d_name );
945                 closedir( dir );
946                 return STATUS_SUCCESS;
947             }
948         }
949     }
950     closedir( dir );
951     goto not_found;  /* avoid warning */
952
953 not_found:
954     unix_name[pos - 1] = 0;
955     return STATUS_OBJECT_PATH_NOT_FOUND;
956 }
957
958
959 /******************************************************************************
960  *           get_dos_device
961  *
962  * Get the Unix path of a DOS device.
963  */
964 static NTSTATUS get_dos_device( const WCHAR *name, UINT name_len, ANSI_STRING *unix_name_ret )
965 {
966     const char *config_dir = wine_get_config_dir();
967     struct stat st;
968     char *unix_name, *new_name, *dev;
969     int i, unix_len;
970
971     /* make sure the device name is ASCII */
972     for (i = 0; i < name_len; i++)
973         if (name[i] <= 32 || name[i] >= 127) return STATUS_OBJECT_NAME_NOT_FOUND;
974
975     unix_len = strlen(config_dir) + sizeof("/dosdevices/") + name_len + 1;
976
977     if (!(unix_name = RtlAllocateHeap( GetProcessHeap(), 0, unix_len )))
978         return STATUS_NO_MEMORY;
979
980     strcpy( unix_name, config_dir );
981     strcat( unix_name, "/dosdevices/" );
982     dev = unix_name + strlen(unix_name);
983
984     for (i = 0; i < name_len; i++) dev[i] = (char)tolowerW(name[i]);
985     dev[i] = 0;
986
987     /* special case for drive devices */
988     if (name_len == 2 && dev[1] == ':')
989     {
990         dev[i++] = ':';
991         dev[i] = 0;
992     }
993
994     for (;;)
995     {
996         if (!stat( unix_name, &st ))
997         {
998             TRACE( "%s -> %s\n", debugstr_wn(name,name_len), debugstr_a(unix_name) );
999             unix_name_ret->Buffer = unix_name;
1000             unix_name_ret->Length = strlen(unix_name);
1001             unix_name_ret->MaximumLength = unix_len;
1002             return STATUS_SUCCESS;
1003         }
1004         if (!dev) break;
1005
1006         /* now try some defaults for it */
1007         if (!strcmp( dev, "aux" ))
1008         {
1009             strcpy( dev, "com1" );
1010             continue;
1011         }
1012         if (!strcmp( dev, "prn" ))
1013         {
1014             strcpy( dev, "lpt1" );
1015             continue;
1016         }
1017         if (!strcmp( dev, "nul" ))
1018         {
1019             strcpy( unix_name, "/dev/null" );
1020             dev = NULL; /* last try */
1021             continue;
1022         }
1023
1024         new_name = NULL;
1025         if (dev[1] == ':' && dev[2] == ':')  /* drive device */
1026         {
1027             dev[2] = 0;  /* remove last ':' to get the drive mount point symlink */
1028             new_name = get_default_drive_device( unix_name );
1029         }
1030         else if (!strncmp( dev, "com", 3 )) new_name = get_default_com_device( dev[3] - '0' );
1031         else if (!strncmp( dev, "lpt", 3 )) new_name = get_default_lpt_device( dev[3] - '0' );
1032
1033         if (!new_name) break;
1034
1035         RtlFreeHeap( GetProcessHeap(), 0, unix_name );
1036         unix_name = new_name;
1037         unix_len = strlen(unix_name) + 1;
1038         dev = NULL; /* last try */
1039     }
1040     RtlFreeHeap( GetProcessHeap(), 0, unix_name );
1041     return STATUS_OBJECT_NAME_NOT_FOUND;
1042 }
1043
1044
1045 /* return the length of the DOS namespace prefix if any */
1046 static inline int get_dos_prefix_len( const UNICODE_STRING *name )
1047 {
1048     static const WCHAR nt_prefixW[] = {'\\','?','?','\\'};
1049     static const WCHAR dosdev_prefixW[] = {'\\','D','o','s','D','e','v','i','c','e','s','\\'};
1050
1051     if (name->Length > sizeof(nt_prefixW) &&
1052         !memcmp( name->Buffer, nt_prefixW, sizeof(nt_prefixW) ))
1053         return sizeof(nt_prefixW) / sizeof(WCHAR);
1054
1055     if (name->Length > sizeof(dosdev_prefixW) &&
1056         !memicmpW( name->Buffer, dosdev_prefixW, sizeof(dosdev_prefixW)/sizeof(WCHAR) ))
1057         return sizeof(dosdev_prefixW) / sizeof(WCHAR);
1058
1059     return 0;
1060 }
1061
1062
1063 /******************************************************************************
1064  *           wine_nt_to_unix_file_name  (NTDLL.@) Not a Windows API
1065  *
1066  * Convert a file name from NT namespace to Unix namespace.
1067  *
1068  * If disposition is not FILE_OPEN or FILE_OVERWRITTE, the last path
1069  * element doesn't have to exist; in that case STATUS_NO_SUCH_FILE is
1070  * returned, but the unix name is still filled in properly.
1071  */
1072 NTSTATUS wine_nt_to_unix_file_name( const UNICODE_STRING *nameW, ANSI_STRING *unix_name_ret,
1073                                     UINT disposition, BOOLEAN check_case )
1074 {
1075     static const WCHAR uncW[] = {'U','N','C','\\'};
1076     static const WCHAR invalid_charsW[] = { INVALID_NT_CHARS, 0 };
1077
1078     NTSTATUS status = STATUS_SUCCESS;
1079     const char *config_dir = wine_get_config_dir();
1080     const WCHAR *name, *p;
1081     struct stat st;
1082     char *unix_name;
1083     int pos, ret, name_len, unix_len, used_default;
1084
1085     name     = nameW->Buffer;
1086     name_len = nameW->Length / sizeof(WCHAR);
1087
1088     if (!name_len || !IS_SEPARATOR(name[0])) return STATUS_OBJECT_PATH_SYNTAX_BAD;
1089
1090     if ((pos = get_dos_prefix_len( nameW )))
1091     {
1092         BOOLEAN is_unc = FALSE;
1093
1094         name += pos;
1095         name_len -= pos;
1096
1097         /* check for UNC prefix */
1098         if (name_len > 4 && !memicmpW( name, uncW, 4 ))
1099         {
1100             name += 3;
1101             name_len -= 3;
1102             is_unc = TRUE;
1103         }
1104         else
1105         {
1106             /* check for a drive letter with path */
1107             if (name_len < 3 || !isalphaW(name[0]) || name[1] != ':' || !IS_SEPARATOR(name[2]))
1108             {
1109                 /* not a drive with path, try other DOS devices */
1110                 return get_dos_device( name, name_len, unix_name_ret );
1111             }
1112             name += 2;  /* skip drive letter */
1113             name_len -= 2;
1114         }
1115
1116         /* check for invalid characters */
1117         for (p = name; p < name + name_len; p++)
1118             if (*p < 32 || strchrW( invalid_charsW, *p )) return STATUS_OBJECT_NAME_INVALID;
1119
1120         unix_len = ntdll_wcstoumbs( 0, name, name_len, NULL, 0, NULL, NULL );
1121         unix_len += MAX_DIR_ENTRY_LEN + 3;
1122         unix_len += strlen(config_dir) + sizeof("/dosdevices/") + 3;
1123         if (!(unix_name = RtlAllocateHeap( GetProcessHeap(), 0, unix_len )))
1124             return STATUS_NO_MEMORY;
1125         strcpy( unix_name, config_dir );
1126         strcat( unix_name, "/dosdevices/" );
1127         pos = strlen(unix_name);
1128         if (is_unc)
1129         {
1130             strcpy( unix_name + pos, "unc" );
1131             pos += 3;
1132         }
1133         else
1134         {
1135             unix_name[pos++] = tolowerW( name[-2] );
1136             unix_name[pos++] = ':';
1137             unix_name[pos] = 0;
1138         }
1139     }
1140     else  /* no DOS prefix, assume NT native name, map directly to Unix */
1141     {
1142         if (!name_len || !IS_SEPARATOR(name[0])) return STATUS_OBJECT_NAME_INVALID;
1143         unix_len = ntdll_wcstoumbs( 0, name, name_len, NULL, 0, NULL, NULL );
1144         unix_len += MAX_DIR_ENTRY_LEN + 3;
1145         if (!(unix_name = RtlAllocateHeap( GetProcessHeap(), 0, unix_len )))
1146             return STATUS_NO_MEMORY;
1147         pos = 0;
1148     }
1149
1150     /* try a shortcut first */
1151
1152     ret = ntdll_wcstoumbs( 0, name, name_len, unix_name + pos, unix_len - pos - 1,
1153                            NULL, &used_default );
1154
1155     while (name_len && IS_SEPARATOR(*name))
1156     {
1157         name++;
1158         name_len--;
1159     }
1160
1161     if (ret > 0 && !used_default)  /* if we used the default char the name didn't convert properly */
1162     {
1163         char *p;
1164         unix_name[pos + ret] = 0;
1165         for (p = unix_name + pos ; *p; p++) if (*p == '\\') *p = '/';
1166         if (!stat( unix_name, &st ))
1167         {
1168             /* creation fails with STATUS_ACCESS_DENIED for the root of the drive */
1169             if (disposition == FILE_CREATE)
1170                 return name_len ? STATUS_OBJECT_NAME_COLLISION : STATUS_ACCESS_DENIED;
1171             goto done;
1172         }
1173     }
1174
1175     if (!name_len)  /* empty name -> drive root doesn't exist */
1176     {
1177         RtlFreeHeap( GetProcessHeap(), 0, unix_name );
1178         return STATUS_OBJECT_PATH_NOT_FOUND;
1179     }
1180     if (check_case && (disposition == FILE_OPEN || disposition == FILE_OVERWRITE))
1181     {
1182         RtlFreeHeap( GetProcessHeap(), 0, unix_name );
1183         return STATUS_OBJECT_NAME_NOT_FOUND;
1184     }
1185
1186     /* now do it component by component */
1187
1188     while (name_len)
1189     {
1190         const WCHAR *end, *next;
1191
1192         end = name;
1193         while (end < name + name_len && !IS_SEPARATOR(*end)) end++;
1194         next = end;
1195         while (next < name + name_len && IS_SEPARATOR(*next)) next++;
1196         name_len -= next - name;
1197
1198         /* grow the buffer if needed */
1199
1200         if (unix_len - pos < MAX_DIR_ENTRY_LEN + 2)
1201         {
1202             char *new_name;
1203             unix_len += 2 * MAX_DIR_ENTRY_LEN;
1204             if (!(new_name = RtlReAllocateHeap( GetProcessHeap(), 0, unix_name, unix_len )))
1205             {
1206                 RtlFreeHeap( GetProcessHeap(), 0, unix_name );
1207                 return STATUS_NO_MEMORY;
1208             }
1209             unix_name = new_name;
1210         }
1211
1212         status = find_file_in_dir( unix_name, pos, name, end - name, check_case );
1213
1214         /* if this is the last element, not finding it is not necessarily fatal */
1215         if (!name_len)
1216         {
1217             if (status == STATUS_OBJECT_PATH_NOT_FOUND)
1218             {
1219                 status = STATUS_OBJECT_NAME_NOT_FOUND;
1220                 if (disposition != FILE_OPEN && disposition != FILE_OVERWRITE)
1221                 {
1222                     ret = ntdll_wcstoumbs( 0, name, end - name, unix_name + pos + 1,
1223                                            MAX_DIR_ENTRY_LEN, NULL, &used_default );
1224                     if (ret > 0 && !used_default)
1225                     {
1226                         unix_name[pos] = '/';
1227                         unix_name[pos + 1 + ret] = 0;
1228                         status = STATUS_NO_SUCH_FILE;
1229                         break;
1230                     }
1231                 }
1232             }
1233             else if (status == STATUS_SUCCESS && disposition == FILE_CREATE)
1234             {
1235                 status = STATUS_OBJECT_NAME_COLLISION;
1236             }
1237         }
1238
1239         if (status != STATUS_SUCCESS)
1240         {
1241             /* couldn't find it at all, fail */
1242             WARN( "%s not found in %s\n", debugstr_w(name), unix_name );
1243             RtlFreeHeap( GetProcessHeap(), 0, unix_name );
1244             return status;
1245         }
1246
1247         pos += strlen( unix_name + pos );
1248         name = next;
1249     }
1250
1251     WARN( "%s -> %s required a case-insensitive search\n",
1252           debugstr_us(nameW), debugstr_a(unix_name) );
1253
1254 done:
1255     TRACE( "%s -> %s\n", debugstr_us(nameW), debugstr_a(unix_name) );
1256     unix_name_ret->Buffer = unix_name;
1257     unix_name_ret->Length = strlen(unix_name);
1258     unix_name_ret->MaximumLength = unix_len;
1259     return status;
1260 }
1261
1262
1263 /******************************************************************
1264  *              RtlDoesFileExists_U   (NTDLL.@)
1265  */
1266 BOOLEAN WINAPI RtlDoesFileExists_U(LPCWSTR file_name)
1267 {
1268     UNICODE_STRING nt_name;
1269     ANSI_STRING unix_name;
1270     BOOLEAN ret;
1271
1272     if (!RtlDosPathNameToNtPathName_U( file_name, &nt_name, NULL, NULL )) return FALSE;
1273     ret = (wine_nt_to_unix_file_name( &nt_name, &unix_name, FILE_OPEN, FALSE ) == STATUS_SUCCESS);
1274     if (ret) RtlFreeAnsiString( &unix_name );
1275     RtlFreeUnicodeString( &nt_name );
1276     return ret;
1277 }