Fixed read_directory_getdents for large directories (found by Rein
[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 /* just in case... */
72 #undef VFAT_IOCTL_READDIR_BOTH
73 #undef USE_GETDENTS
74
75 #ifdef linux
76
77 /* We want the real kernel dirent structure, not the libc one */
78 typedef struct
79 {
80     long d_ino;
81     long d_off;
82     unsigned short d_reclen;
83     char d_name[256];
84 } KERNEL_DIRENT;
85
86 /* Define the VFAT ioctl to get both short and long file names */
87 #define VFAT_IOCTL_READDIR_BOTH  _IOR('r', 1, KERNEL_DIRENT [2] )
88
89 #ifndef O_DIRECTORY
90 # define O_DIRECTORY 0200000 /* must be directory */
91 #endif
92
93 #ifdef __i386__
94
95 typedef struct
96 {
97     ULONG64        d_ino;
98     LONG64         d_off;
99     unsigned short d_reclen;
100     unsigned char  d_type;
101     char           d_name[256];
102 } KERNEL_DIRENT64;
103
104 static inline int getdents64( int fd, KERNEL_DIRENT64 *de, unsigned int size )
105 {
106     int ret;
107     __asm__( "pushl %%ebx; movl %2,%%ebx; int $0x80; popl %%ebx"
108              : "=a" (ret)
109              : "0" (220 /*NR_getdents64*/), "r" (fd), "c" (de), "d" (size)
110              : "memory" );
111     if (ret < 0)
112     {
113         errno = -ret;
114         ret = -1;
115     }
116     return ret;
117 }
118 #define USE_GETDENTS
119
120 #endif  /* i386 */
121
122 #endif  /* linux */
123
124 #define IS_OPTION_TRUE(ch) ((ch) == 'y' || (ch) == 'Y' || (ch) == 't' || (ch) == 'T' || (ch) == '1')
125 #define IS_SEPARATOR(ch)   ((ch) == '\\' || (ch) == '/')
126
127 #define INVALID_NT_CHARS   '*','?','<','>','|','"'
128 #define INVALID_DOS_CHARS  INVALID_NT_CHARS,'+','=',',',';','[',']',' ','\345'
129
130 #define MAX_DIR_ENTRY_LEN 255  /* max length of a directory entry in chars */
131
132 static int show_dir_symlinks = -1;
133 static int show_dot_files;
134
135 /* at some point we may want to allow Winelib apps to set this */
136 static const int is_case_sensitive = FALSE;
137
138 static CRITICAL_SECTION dir_section;
139 static CRITICAL_SECTION_DEBUG critsect_debug =
140 {
141     0, 0, &dir_section,
142     { &critsect_debug.ProcessLocksList, &critsect_debug.ProcessLocksList },
143       0, 0, { 0, (DWORD)(__FILE__ ": dir_section") }
144 };
145 static CRITICAL_SECTION dir_section = { &critsect_debug, -1, 0, 0, 0, 0 };
146
147
148 /***********************************************************************
149  *           get_default_com_device
150  *
151  * Return the default device to use for serial ports.
152  */
153 static char *get_default_com_device( int num )
154 {
155     char *ret = NULL;
156
157     if (!num || num > 9) return ret;
158 #ifdef linux
159     ret = RtlAllocateHeap( GetProcessHeap(), 0, sizeof("/dev/ttyS0") );
160     if (ret)
161     {
162         strcpy( ret, "/dev/ttyS0" );
163         ret[strlen(ret) - 1] = '0' + num - 1;
164     }
165 #else
166     FIXME( "no known default for device com%d\n", num );
167 #endif
168     return ret;
169 }
170
171
172 /***********************************************************************
173  *           get_default_lpt_device
174  *
175  * Return the default device to use for parallel ports.
176  */
177 static char *get_default_lpt_device( int num )
178 {
179     char *ret = NULL;
180
181     if (!num || num > 9) return ret;
182 #ifdef linux
183     ret = RtlAllocateHeap( GetProcessHeap(), 0, sizeof("/dev/lp0") );
184     if (ret)
185     {
186         strcpy( ret, "/dev/lp0" );
187         ret[strlen(ret) - 1] = '0' + num - 1;
188     }
189 #else
190     FIXME( "no known default for device lpt%d\n", num );
191 #endif
192     return ret;
193 }
194
195
196 /***********************************************************************
197  *           parse_mount_entries
198  *
199  * Parse mount entries looking for a given device. Helper for get_default_drive_device.
200  */
201 #ifdef linux
202 static char *parse_mount_entries( FILE *f, dev_t dev, ino_t ino )
203 {
204     struct mntent *entry;
205     struct stat st;
206     char *device;
207
208     while ((entry = getmntent( f )))
209     {
210         /* don't even bother stat'ing network mounts, there's no meaningful device anyway */
211         if (!strcmp( entry->mnt_type, "nfs" ) ||
212             !strcmp( entry->mnt_type, "smbfs" ) ||
213             !strcmp( entry->mnt_type, "ncpfs" )) continue;
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  *           read_directory_vfat
644  *
645  * Read a directory using the VFAT ioctl; helper for NtQueryDirectoryFile.
646  */
647 #ifdef VFAT_IOCTL_READDIR_BOTH
648 static int read_directory_vfat( int fd, IO_STATUS_BLOCK *io, void *buffer, ULONG length,
649                                 BOOLEAN single_entry, const UNICODE_STRING *mask,
650                                 BOOLEAN restart_scan )
651
652 {
653     int res;
654     KERNEL_DIRENT de[2];
655     FILE_BOTH_DIR_INFORMATION *info, *last_info = NULL;
656     static const unsigned int max_dir_info_size = sizeof(*info) + (MAX_DIR_ENTRY_LEN-1) * sizeof(WCHAR);
657
658     io->u.Status = STATUS_SUCCESS;
659
660     if (restart_scan) lseek( fd, 0, SEEK_SET );
661
662     if (length < max_dir_info_size)  /* we may have to return a partial entry here */
663     {
664         off_t old_pos = lseek( fd, 0, SEEK_CUR );
665
666         res = ioctl( fd, VFAT_IOCTL_READDIR_BOTH, (long)de );
667         if (res == -1 && errno != ENOENT) return -1;  /* VFAT ioctl probably not supported */
668
669         while (res != -1)
670         {
671             if (!de[0].d_reclen) break;
672             if (de[1].d_name[0])
673                 info = append_entry( buffer, &io->Information, length,
674                                      de[1].d_name, de[0].d_name, mask );
675             else
676                 info = append_entry( buffer, &io->Information, length,
677                                      de[0].d_name, NULL, mask );
678             if (info)
679             {
680                 last_info = info;
681                 if ((char *)info->FileName + info->FileNameLength > (char *)buffer + length)
682                 {
683                     io->u.Status = STATUS_BUFFER_OVERFLOW;
684                     lseek( fd, old_pos, SEEK_SET );  /* restore pos to previous entry */
685                 }
686                 break;
687             }
688             old_pos = lseek( fd, 0, SEEK_CUR );
689             res = ioctl( fd, VFAT_IOCTL_READDIR_BOTH, (long)de );
690         }
691     }
692     else  /* we'll only return full entries, no need to worry about overflow */
693     {
694         res = ioctl( fd, VFAT_IOCTL_READDIR_BOTH, (long)de );
695         if (res == -1 && errno != ENOENT) return -1;  /* VFAT ioctl probably not supported */
696
697         while (res != -1)
698         {
699             if (!de[0].d_reclen) break;
700             if (de[1].d_name[0])
701                 info = append_entry( buffer, &io->Information, length,
702                                      de[1].d_name, de[0].d_name, mask );
703             else
704                 info = append_entry( buffer, &io->Information, length,
705                                      de[0].d_name, NULL, mask );
706             if (info)
707             {
708                 last_info = info;
709                 if (single_entry) break;
710                 /* check if we still have enough space for the largest possible entry */
711                 if (io->Information + max_dir_info_size > length) break;
712             }
713             res = ioctl( fd, VFAT_IOCTL_READDIR_BOTH, (long)de );
714         }
715     }
716
717     if (last_info) last_info->NextEntryOffset = 0;
718     else io->u.Status = restart_scan ? STATUS_NO_SUCH_FILE : STATUS_NO_MORE_FILES;
719     return 0;
720 }
721 #endif /* VFAT_IOCTL_READDIR_BOTH */
722
723
724 /***********************************************************************
725  *           read_directory_getdents
726  *
727  * Read a directory using the Linux getdents64 system call; helper for NtQueryDirectoryFile.
728  */
729 #ifdef USE_GETDENTS
730 static int read_directory_getdents( int fd, IO_STATUS_BLOCK *io, void *buffer, ULONG length,
731                                     BOOLEAN single_entry, const UNICODE_STRING *mask,
732                                     BOOLEAN restart_scan )
733 {
734     off_t old_pos = 0;
735     size_t size = length;
736     int res;
737     char local_buffer[8192];
738     KERNEL_DIRENT64 *data, *de;
739     FILE_BOTH_DIR_INFORMATION *info, *last_info = NULL;
740     static const unsigned int max_dir_info_size = sizeof(*info) + (MAX_DIR_ENTRY_LEN-1) * sizeof(WCHAR);
741
742     if (size <= sizeof(local_buffer) || !(data = RtlAllocateHeap( GetProcessHeap(), 0, size )))
743     {
744         size = sizeof(local_buffer);
745         data = (KERNEL_DIRENT64 *)local_buffer;
746     }
747
748     if (restart_scan) lseek( fd, 0, SEEK_SET );
749     else if (length < max_dir_info_size)  /* we may have to return a partial entry here */
750     {
751         old_pos = lseek( fd, 0, SEEK_CUR );
752         if (old_pos == -1 && errno == ENOENT)
753         {
754             io->u.Status = STATUS_NO_MORE_FILES;
755             res = 0;
756             goto done;
757         }
758     }
759
760     io->u.Status = STATUS_SUCCESS;
761
762     res = getdents64( fd, data, size );
763     if (res == -1)
764     {
765         if (errno != ENOSYS)
766         {
767             io->u.Status = FILE_GetNtStatus();
768             res = 0;
769         }
770         goto done;
771     }
772
773     de = data;
774
775     while (res > 0)
776     {
777         res -= de->d_reclen;
778         info = append_entry( buffer, &io->Information, length, de->d_name, NULL, mask );
779         if (info)
780         {
781             last_info = info;
782             if ((char *)info->FileName + info->FileNameLength > (char *)buffer + length)
783             {
784                 io->u.Status = STATUS_BUFFER_OVERFLOW;
785                 lseek( fd, old_pos, SEEK_SET );  /* restore pos to previous entry */
786                 break;
787             }
788             /* check if we still have enough space for the largest possible entry */
789             if (single_entry || io->Information + max_dir_info_size > length)
790             {
791                 if (res > 0) lseek( fd, de->d_off, SEEK_SET );  /* set pos to next entry */
792                 break;
793             }
794         }
795         old_pos = de->d_off;
796         /* move on to the next entry */
797         if (res > 0) de = (KERNEL_DIRENT64 *)((char *)de + de->d_reclen);
798         else
799         {
800             res = getdents64( fd, data, size );
801             de = data;
802         }
803     }
804
805     if (last_info) last_info->NextEntryOffset = 0;
806     else io->u.Status = restart_scan ? STATUS_NO_SUCH_FILE : STATUS_NO_MORE_FILES;
807     res = 0;
808 done:
809     if ((char *)data != local_buffer) RtlFreeHeap( GetProcessHeap(), 0, data );
810     return res;
811 }
812 #endif  /* USE_GETDENTS */
813
814
815 /***********************************************************************
816  *           read_directory_readdir
817  *
818  * Read a directory using the POSIX readdir interface; helper for NtQueryDirectoryFile.
819  */
820 static void read_directory_readdir( int fd, IO_STATUS_BLOCK *io, void *buffer, ULONG length,
821                                     BOOLEAN single_entry, const UNICODE_STRING *mask,
822                                     BOOLEAN restart_scan )
823 {
824     DIR *dir;
825     off_t i, old_pos = 0;
826     struct dirent *de;
827     FILE_BOTH_DIR_INFORMATION *info, *last_info = NULL;
828     static const unsigned int max_dir_info_size = sizeof(*info) + (MAX_DIR_ENTRY_LEN-1) * sizeof(WCHAR);
829
830     if (!(dir = opendir( "." )))
831     {
832         io->u.Status = FILE_GetNtStatus();
833         return;
834     }
835
836     if (!restart_scan)
837     {
838         old_pos = lseek( fd, 0, SEEK_CUR );
839         /* skip the right number of entries */
840         for (i = 0; i < old_pos; i++)
841         {
842             if (!readdir( dir ))
843             {
844                 closedir( dir );
845                 io->u.Status = STATUS_NO_MORE_FILES;
846                 return;
847             }
848         }
849     }
850     io->u.Status = STATUS_SUCCESS;
851
852     while ((de = readdir( dir )))
853     {
854         old_pos++;
855         info = append_entry( buffer, &io->Information, length, de->d_name, NULL, mask );
856         if (info)
857         {
858             last_info = info;
859             if ((char *)info->FileName + info->FileNameLength > (char *)buffer + length)
860             {
861                 io->u.Status = STATUS_BUFFER_OVERFLOW;
862                 old_pos--;  /* restore pos to previous entry */
863                 break;
864             }
865             if (single_entry) break;
866             /* check if we still have enough space for the largest possible entry */
867             if (io->Information + max_dir_info_size > length) break;
868         }
869     }
870
871     lseek( fd, old_pos, SEEK_SET );  /* store dir offset as filepos for fd */
872     closedir( dir );
873
874     if (last_info) last_info->NextEntryOffset = 0;
875     else io->u.Status = restart_scan ? STATUS_NO_SUCH_FILE : STATUS_NO_MORE_FILES;
876 }
877
878
879 /******************************************************************************
880  *  NtQueryDirectoryFile        [NTDLL.@]
881  *  ZwQueryDirectoryFile        [NTDLL.@]
882  */
883 NTSTATUS WINAPI NtQueryDirectoryFile( HANDLE handle, HANDLE event,
884                                       PIO_APC_ROUTINE apc_routine, PVOID apc_context,
885                                       PIO_STATUS_BLOCK io,
886                                       PVOID buffer, ULONG length,
887                                       FILE_INFORMATION_CLASS info_class,
888                                       BOOLEAN single_entry,
889                                       PUNICODE_STRING mask,
890                                       BOOLEAN restart_scan )
891 {
892     int cwd, fd;
893
894     TRACE("(%p %p %p %p %p %p 0x%08lx 0x%08x 0x%08x %s 0x%08x\n",
895           handle, event, apc_routine, apc_context, io, buffer,
896           length, info_class, single_entry, debugstr_us(mask),
897           restart_scan);
898
899     if (length < sizeof(FILE_BOTH_DIR_INFORMATION)) return STATUS_INFO_LENGTH_MISMATCH;
900
901     if (event || apc_routine)
902     {
903         FIXME( "Unsupported yet option\n" );
904         return io->u.Status = STATUS_NOT_IMPLEMENTED;
905     }
906     if (info_class != FileBothDirectoryInformation)
907     {
908         FIXME( "Unsupported file info class %d\n", info_class );
909         return io->u.Status = STATUS_NOT_IMPLEMENTED;
910     }
911
912     if ((io->u.Status = wine_server_handle_to_fd( handle, GENERIC_READ, &fd, NULL )) != STATUS_SUCCESS)
913         return io->u.Status;
914
915     io->Information = 0;
916
917     RtlEnterCriticalSection( &dir_section );
918
919     if (show_dir_symlinks == -1) init_options();
920
921     if ((cwd = open(".", O_RDONLY)) != -1 && fchdir( fd ) != -1)
922     {
923 #ifdef VFAT_IOCTL_READDIR_BOTH
924         if ((read_directory_vfat( fd, io, buffer, length, single_entry, mask, restart_scan )) == -1)
925 #endif
926 #ifdef USE_GETDENTS
927             if ((read_directory_getdents( fd, io, buffer, length, single_entry, mask, restart_scan )) == -1)
928 #endif
929                 read_directory_readdir( fd, io, buffer, length, single_entry, mask, restart_scan );
930
931         if (fchdir( cwd ) == -1) chdir( "/" );
932     }
933     else io->u.Status = FILE_GetNtStatus();
934
935     RtlLeaveCriticalSection( &dir_section );
936
937     wine_server_release_fd( handle, fd );
938     if (cwd != -1) close( cwd );
939     TRACE( "=> %lx (%ld)\n", io->u.Status, io->Information );
940     return io->u.Status;
941 }
942
943
944 /***********************************************************************
945  *           find_file_in_dir
946  *
947  * Find a file in a directory the hard way, by doing a case-insensitive search.
948  * The file found is appended to unix_name at pos.
949  * There must be at least MAX_DIR_ENTRY_LEN+2 chars available at pos.
950  */
951 static NTSTATUS find_file_in_dir( char *unix_name, int pos, const WCHAR *name, int length,
952                                   int check_case )
953 {
954     WCHAR buffer[MAX_DIR_ENTRY_LEN];
955     UNICODE_STRING str;
956     BOOLEAN spaces;
957     DIR *dir;
958     struct dirent *de;
959     struct stat st;
960     int ret, used_default, is_name_8_dot_3;
961
962     /* try a shortcut for this directory */
963
964     unix_name[pos++] = '/';
965     ret = ntdll_wcstoumbs( 0, name, length, unix_name + pos, MAX_DIR_ENTRY_LEN,
966                            NULL, &used_default );
967     /* if we used the default char, the Unix name won't round trip properly back to Unicode */
968     /* so it cannot match the file we are looking for */
969     if (ret >= 0 && !used_default)
970     {
971         unix_name[pos + ret] = 0;
972         if (!stat( unix_name, &st )) return STATUS_SUCCESS;
973     }
974     if (check_case) goto not_found;  /* we want an exact match */
975
976     if (pos > 1) unix_name[pos - 1] = 0;
977     else unix_name[1] = 0;  /* keep the initial slash */
978
979     /* check if it fits in 8.3 so that we don't look for short names if we won't need them */
980
981     str.Buffer = (WCHAR *)name;
982     str.Length = length * sizeof(WCHAR);
983     str.MaximumLength = str.Length;
984     is_name_8_dot_3 = RtlIsNameLegalDOS8Dot3( &str, NULL, &spaces ) && !spaces;
985
986     /* now look for it through the directory */
987
988 #ifdef VFAT_IOCTL_READDIR_BOTH
989     if (is_name_8_dot_3)
990     {
991         int fd = open( unix_name, O_RDONLY | O_DIRECTORY );
992         if (fd != -1)
993         {
994             KERNEL_DIRENT de[2];
995
996             if (ioctl( fd, VFAT_IOCTL_READDIR_BOTH, (long)de ) != -1)
997             {
998                 unix_name[pos - 1] = '/';
999                 for (;;)
1000                 {
1001                     if (!de[0].d_reclen) break;
1002
1003                     if (de[1].d_name[0])
1004                     {
1005                         ret = ntdll_umbstowcs( 0, de[1].d_name, strlen(de[1].d_name),
1006                                                buffer, MAX_DIR_ENTRY_LEN );
1007                         if (ret == length && !memicmpW( buffer, name, length))
1008                         {
1009                             strcpy( unix_name + pos, de[1].d_name );
1010                             close( fd );
1011                             return STATUS_SUCCESS;
1012                         }
1013                     }
1014                     ret = ntdll_umbstowcs( 0, de[0].d_name, strlen(de[0].d_name),
1015                                            buffer, MAX_DIR_ENTRY_LEN );
1016                     if (ret == length && !memicmpW( buffer, name, length))
1017                     {
1018                         strcpy( unix_name + pos,
1019                                 de[1].d_name[0] ? de[1].d_name : de[0].d_name );
1020                         close( fd );
1021                         return STATUS_SUCCESS;
1022                     }
1023                     if (ioctl( fd, VFAT_IOCTL_READDIR_BOTH, (long)de ) == -1)
1024                     {
1025                         close( fd );
1026                         goto not_found;
1027                     }
1028                 }
1029             }
1030             close( fd );
1031         }
1032         /* fall through to normal handling */
1033     }
1034 #endif /* VFAT_IOCTL_READDIR_BOTH */
1035
1036     if (!(dir = opendir( unix_name )))
1037     {
1038         if (errno == ENOENT) return STATUS_OBJECT_PATH_NOT_FOUND;
1039         else return FILE_GetNtStatus();
1040     }
1041     unix_name[pos - 1] = '/';
1042     str.Buffer = buffer;
1043     str.MaximumLength = sizeof(buffer);
1044     while ((de = readdir( dir )))
1045     {
1046         ret = ntdll_umbstowcs( 0, de->d_name, strlen(de->d_name), buffer, MAX_DIR_ENTRY_LEN );
1047         if (ret == length && !memicmpW( buffer, name, length ))
1048         {
1049             strcpy( unix_name + pos, de->d_name );
1050             closedir( dir );
1051             return STATUS_SUCCESS;
1052         }
1053
1054         if (!is_name_8_dot_3) continue;
1055
1056         str.Length = ret * sizeof(WCHAR);
1057         if (!RtlIsNameLegalDOS8Dot3( &str, NULL, &spaces ) || spaces)
1058         {
1059             WCHAR short_nameW[12];
1060             ret = hash_short_file_name( &str, short_nameW );
1061             if (ret == length && !memicmpW( short_nameW, name, length ))
1062             {
1063                 strcpy( unix_name + pos, de->d_name );
1064                 closedir( dir );
1065                 return STATUS_SUCCESS;
1066             }
1067         }
1068     }
1069     closedir( dir );
1070     goto not_found;  /* avoid warning */
1071
1072 not_found:
1073     unix_name[pos - 1] = 0;
1074     return STATUS_OBJECT_PATH_NOT_FOUND;
1075 }
1076
1077
1078 /******************************************************************************
1079  *           get_dos_device
1080  *
1081  * Get the Unix path of a DOS device.
1082  */
1083 static NTSTATUS get_dos_device( const WCHAR *name, UINT name_len, ANSI_STRING *unix_name_ret )
1084 {
1085     const char *config_dir = wine_get_config_dir();
1086     struct stat st;
1087     char *unix_name, *new_name, *dev;
1088     unsigned int i;
1089     int unix_len;
1090
1091     /* make sure the device name is ASCII */
1092     for (i = 0; i < name_len; i++)
1093         if (name[i] <= 32 || name[i] >= 127) return STATUS_OBJECT_NAME_NOT_FOUND;
1094
1095     unix_len = strlen(config_dir) + sizeof("/dosdevices/") + name_len + 1;
1096
1097     if (!(unix_name = RtlAllocateHeap( GetProcessHeap(), 0, unix_len )))
1098         return STATUS_NO_MEMORY;
1099
1100     strcpy( unix_name, config_dir );
1101     strcat( unix_name, "/dosdevices/" );
1102     dev = unix_name + strlen(unix_name);
1103
1104     for (i = 0; i < name_len; i++) dev[i] = (char)tolowerW(name[i]);
1105     dev[i] = 0;
1106
1107     /* special case for drive devices */
1108     if (name_len == 2 && dev[1] == ':')
1109     {
1110         dev[i++] = ':';
1111         dev[i] = 0;
1112     }
1113
1114     for (;;)
1115     {
1116         if (!stat( unix_name, &st ))
1117         {
1118             TRACE( "%s -> %s\n", debugstr_wn(name,name_len), debugstr_a(unix_name) );
1119             unix_name_ret->Buffer = unix_name;
1120             unix_name_ret->Length = strlen(unix_name);
1121             unix_name_ret->MaximumLength = unix_len;
1122             return STATUS_SUCCESS;
1123         }
1124         if (!dev) break;
1125
1126         /* now try some defaults for it */
1127         if (!strcmp( dev, "aux" ))
1128         {
1129             strcpy( dev, "com1" );
1130             continue;
1131         }
1132         if (!strcmp( dev, "prn" ))
1133         {
1134             strcpy( dev, "lpt1" );
1135             continue;
1136         }
1137         if (!strcmp( dev, "nul" ))
1138         {
1139             strcpy( unix_name, "/dev/null" );
1140             dev = NULL; /* last try */
1141             continue;
1142         }
1143
1144         new_name = NULL;
1145         if (dev[1] == ':' && dev[2] == ':')  /* drive device */
1146         {
1147             dev[2] = 0;  /* remove last ':' to get the drive mount point symlink */
1148             new_name = get_default_drive_device( unix_name );
1149         }
1150         else if (!strncmp( dev, "com", 3 )) new_name = get_default_com_device( dev[3] - '0' );
1151         else if (!strncmp( dev, "lpt", 3 )) new_name = get_default_lpt_device( dev[3] - '0' );
1152
1153         if (!new_name) break;
1154
1155         RtlFreeHeap( GetProcessHeap(), 0, unix_name );
1156         unix_name = new_name;
1157         unix_len = strlen(unix_name) + 1;
1158         dev = NULL; /* last try */
1159     }
1160     RtlFreeHeap( GetProcessHeap(), 0, unix_name );
1161     return STATUS_OBJECT_NAME_NOT_FOUND;
1162 }
1163
1164
1165 /* return the length of the DOS namespace prefix if any */
1166 static inline int get_dos_prefix_len( const UNICODE_STRING *name )
1167 {
1168     static const WCHAR nt_prefixW[] = {'\\','?','?','\\'};
1169     static const WCHAR dosdev_prefixW[] = {'\\','D','o','s','D','e','v','i','c','e','s','\\'};
1170
1171     if (name->Length > sizeof(nt_prefixW) &&
1172         !memcmp( name->Buffer, nt_prefixW, sizeof(nt_prefixW) ))
1173         return sizeof(nt_prefixW) / sizeof(WCHAR);
1174
1175     if (name->Length > sizeof(dosdev_prefixW) &&
1176         !memicmpW( name->Buffer, dosdev_prefixW, sizeof(dosdev_prefixW)/sizeof(WCHAR) ))
1177         return sizeof(dosdev_prefixW) / sizeof(WCHAR);
1178
1179     return 0;
1180 }
1181
1182
1183 /******************************************************************************
1184  *           wine_nt_to_unix_file_name  (NTDLL.@) Not a Windows API
1185  *
1186  * Convert a file name from NT namespace to Unix namespace.
1187  *
1188  * If disposition is not FILE_OPEN or FILE_OVERWRITTE, the last path
1189  * element doesn't have to exist; in that case STATUS_NO_SUCH_FILE is
1190  * returned, but the unix name is still filled in properly.
1191  */
1192 NTSTATUS wine_nt_to_unix_file_name( const UNICODE_STRING *nameW, ANSI_STRING *unix_name_ret,
1193                                     UINT disposition, BOOLEAN check_case )
1194 {
1195     static const WCHAR uncW[] = {'U','N','C','\\'};
1196     static const WCHAR invalid_charsW[] = { INVALID_NT_CHARS, 0 };
1197
1198     NTSTATUS status = STATUS_SUCCESS;
1199     const char *config_dir = wine_get_config_dir();
1200     const WCHAR *name, *p;
1201     struct stat st;
1202     char *unix_name;
1203     int pos, ret, name_len, unix_len, used_default;
1204
1205     name     = nameW->Buffer;
1206     name_len = nameW->Length / sizeof(WCHAR);
1207
1208     if (!name_len || !IS_SEPARATOR(name[0])) return STATUS_OBJECT_PATH_SYNTAX_BAD;
1209
1210     if ((pos = get_dos_prefix_len( nameW )))
1211     {
1212         BOOLEAN is_unc = FALSE;
1213
1214         name += pos;
1215         name_len -= pos;
1216
1217         /* check for UNC prefix */
1218         if (name_len > 4 && !memicmpW( name, uncW, 4 ))
1219         {
1220             name += 3;
1221             name_len -= 3;
1222             is_unc = TRUE;
1223         }
1224         else
1225         {
1226             /* check for a drive letter with path */
1227             if (name_len < 3 || !isalphaW(name[0]) || name[1] != ':' || !IS_SEPARATOR(name[2]))
1228             {
1229                 /* not a drive with path, try other DOS devices */
1230                 return get_dos_device( name, name_len, unix_name_ret );
1231             }
1232             name += 2;  /* skip drive letter */
1233             name_len -= 2;
1234         }
1235
1236         /* check for invalid characters */
1237         for (p = name; p < name + name_len; p++)
1238             if (*p < 32 || strchrW( invalid_charsW, *p )) return STATUS_OBJECT_NAME_INVALID;
1239
1240         unix_len = ntdll_wcstoumbs( 0, name, name_len, NULL, 0, NULL, NULL );
1241         unix_len += MAX_DIR_ENTRY_LEN + 3;
1242         unix_len += strlen(config_dir) + sizeof("/dosdevices/") + 3;
1243         if (!(unix_name = RtlAllocateHeap( GetProcessHeap(), 0, unix_len )))
1244             return STATUS_NO_MEMORY;
1245         strcpy( unix_name, config_dir );
1246         strcat( unix_name, "/dosdevices/" );
1247         pos = strlen(unix_name);
1248         if (is_unc)
1249         {
1250             strcpy( unix_name + pos, "unc" );
1251             pos += 3;
1252         }
1253         else
1254         {
1255             unix_name[pos++] = tolowerW( name[-2] );
1256             unix_name[pos++] = ':';
1257             unix_name[pos] = 0;
1258         }
1259     }
1260     else  /* no DOS prefix, assume NT native name, map directly to Unix */
1261     {
1262         if (!name_len || !IS_SEPARATOR(name[0])) return STATUS_OBJECT_NAME_INVALID;
1263         unix_len = ntdll_wcstoumbs( 0, name, name_len, NULL, 0, NULL, NULL );
1264         unix_len += MAX_DIR_ENTRY_LEN + 3;
1265         if (!(unix_name = RtlAllocateHeap( GetProcessHeap(), 0, unix_len )))
1266             return STATUS_NO_MEMORY;
1267         pos = 0;
1268     }
1269
1270     /* try a shortcut first */
1271
1272     ret = ntdll_wcstoumbs( 0, name, name_len, unix_name + pos, unix_len - pos - 1,
1273                            NULL, &used_default );
1274
1275     while (name_len && IS_SEPARATOR(*name))
1276     {
1277         name++;
1278         name_len--;
1279     }
1280
1281     if (ret > 0 && !used_default)  /* if we used the default char the name didn't convert properly */
1282     {
1283         char *p;
1284         unix_name[pos + ret] = 0;
1285         for (p = unix_name + pos ; *p; p++) if (*p == '\\') *p = '/';
1286         if (!stat( unix_name, &st ))
1287         {
1288             /* creation fails with STATUS_ACCESS_DENIED for the root of the drive */
1289             if (disposition == FILE_CREATE)
1290                 return name_len ? STATUS_OBJECT_NAME_COLLISION : STATUS_ACCESS_DENIED;
1291             goto done;
1292         }
1293     }
1294
1295     if (!name_len)  /* empty name -> drive root doesn't exist */
1296     {
1297         RtlFreeHeap( GetProcessHeap(), 0, unix_name );
1298         return STATUS_OBJECT_PATH_NOT_FOUND;
1299     }
1300     if (check_case && (disposition == FILE_OPEN || disposition == FILE_OVERWRITE))
1301     {
1302         RtlFreeHeap( GetProcessHeap(), 0, unix_name );
1303         return STATUS_OBJECT_NAME_NOT_FOUND;
1304     }
1305
1306     /* now do it component by component */
1307
1308     while (name_len)
1309     {
1310         const WCHAR *end, *next;
1311
1312         end = name;
1313         while (end < name + name_len && !IS_SEPARATOR(*end)) end++;
1314         next = end;
1315         while (next < name + name_len && IS_SEPARATOR(*next)) next++;
1316         name_len -= next - name;
1317
1318         /* grow the buffer if needed */
1319
1320         if (unix_len - pos < MAX_DIR_ENTRY_LEN + 2)
1321         {
1322             char *new_name;
1323             unix_len += 2 * MAX_DIR_ENTRY_LEN;
1324             if (!(new_name = RtlReAllocateHeap( GetProcessHeap(), 0, unix_name, unix_len )))
1325             {
1326                 RtlFreeHeap( GetProcessHeap(), 0, unix_name );
1327                 return STATUS_NO_MEMORY;
1328             }
1329             unix_name = new_name;
1330         }
1331
1332         status = find_file_in_dir( unix_name, pos, name, end - name, check_case );
1333
1334         /* if this is the last element, not finding it is not necessarily fatal */
1335         if (!name_len)
1336         {
1337             if (status == STATUS_OBJECT_PATH_NOT_FOUND)
1338             {
1339                 status = STATUS_OBJECT_NAME_NOT_FOUND;
1340                 if (disposition != FILE_OPEN && disposition != FILE_OVERWRITE)
1341                 {
1342                     ret = ntdll_wcstoumbs( 0, name, end - name, unix_name + pos + 1,
1343                                            MAX_DIR_ENTRY_LEN, NULL, &used_default );
1344                     if (ret > 0 && !used_default)
1345                     {
1346                         unix_name[pos] = '/';
1347                         unix_name[pos + 1 + ret] = 0;
1348                         status = STATUS_NO_SUCH_FILE;
1349                         break;
1350                     }
1351                 }
1352             }
1353             else if (status == STATUS_SUCCESS && disposition == FILE_CREATE)
1354             {
1355                 status = STATUS_OBJECT_NAME_COLLISION;
1356             }
1357         }
1358
1359         if (status != STATUS_SUCCESS)
1360         {
1361             /* couldn't find it at all, fail */
1362             WARN( "%s not found in %s\n", debugstr_w(name), unix_name );
1363             RtlFreeHeap( GetProcessHeap(), 0, unix_name );
1364             return status;
1365         }
1366
1367         pos += strlen( unix_name + pos );
1368         name = next;
1369     }
1370
1371     WARN( "%s -> %s required a case-insensitive search\n",
1372           debugstr_us(nameW), debugstr_a(unix_name) );
1373
1374 done:
1375     TRACE( "%s -> %s\n", debugstr_us(nameW), debugstr_a(unix_name) );
1376     unix_name_ret->Buffer = unix_name;
1377     unix_name_ret->Length = strlen(unix_name);
1378     unix_name_ret->MaximumLength = unix_len;
1379     return status;
1380 }
1381
1382
1383 /******************************************************************
1384  *              RtlDoesFileExists_U   (NTDLL.@)
1385  */
1386 BOOLEAN WINAPI RtlDoesFileExists_U(LPCWSTR file_name)
1387 {
1388     UNICODE_STRING nt_name;
1389     ANSI_STRING unix_name;
1390     BOOLEAN ret;
1391
1392     if (!RtlDosPathNameToNtPathName_U( file_name, &nt_name, NULL, NULL )) return FALSE;
1393     ret = (wine_nt_to_unix_file_name( &nt_name, &unix_name, FILE_OPEN, FALSE ) == STATUS_SUCCESS);
1394     if (ret) RtlFreeAnsiString( &unix_name );
1395     RtlFreeUnicodeString( &nt_name );
1396     return ret;
1397 }