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