Implemented NtCreatelFile using the new symlink scheme.
[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 <sys/stat.h>
34 #ifdef HAVE_SYS_IOCTL_H
35 #include <sys/ioctl.h>
36 #endif
37 #ifdef HAVE_LINUX_IOCTL_H
38 #include <linux/ioctl.h>
39 #endif
40 #include <time.h>
41 #ifdef HAVE_UNISTD_H
42 # include <unistd.h>
43 #endif
44
45 #define NONAMELESSUNION
46 #define NONAMELESSSTRUCT
47 #include "windef.h"
48 #include "winbase.h"
49 #include "winnt.h"
50 #include "winreg.h"
51 #include "ntstatus.h"
52 #include "winternl.h"
53 #include "ntdll_misc.h"
54 #include "wine/unicode.h"
55 #include "wine/server.h"
56 #include "wine/library.h"
57 #include "wine/debug.h"
58
59 WINE_DEFAULT_DEBUG_CHANNEL(file);
60
61 /* Define the VFAT ioctl to get both short and long file names */
62 /* FIXME: is it possible to get this to work on other systems? */
63 #ifdef linux
64 /* We want the real kernel dirent structure, not the libc one */
65 typedef struct
66 {
67     long d_ino;
68     long d_off;
69     unsigned short d_reclen;
70     char d_name[256];
71 } KERNEL_DIRENT;
72
73 #define VFAT_IOCTL_READDIR_BOTH  _IOR('r', 1, KERNEL_DIRENT [2] )
74
75 #ifndef O_DIRECTORY
76 # define O_DIRECTORY 0200000 /* must be directory */
77 #endif
78
79 #else   /* linux */
80 #undef VFAT_IOCTL_READDIR_BOTH  /* just in case... */
81 #endif  /* linux */
82
83 #define IS_OPTION_TRUE(ch) ((ch) == 'y' || (ch) == 'Y' || (ch) == 't' || (ch) == 'T' || (ch) == '1')
84 #define IS_SEPARATOR(ch)   ((ch) == '\\' || (ch) == '/')
85
86 #define INVALID_DOS_CHARS  '*','?','<','>','|','"','+','=',',',';','[',']',' ','\345'
87
88 #define MAX_DIR_ENTRY_LEN 255  /* max length of a directory entry in chars */
89
90 static int show_dir_symlinks = -1;
91 static int show_dot_files;
92
93 /* at some point we may want to allow Winelib apps to set this */
94 static const int is_case_sensitive = FALSE;
95
96 static CRITICAL_SECTION chdir_section;
97 static CRITICAL_SECTION_DEBUG critsect_debug =
98 {
99     0, 0, &chdir_section,
100     { &critsect_debug.ProcessLocksList, &critsect_debug.ProcessLocksList },
101       0, 0, { 0, (DWORD)(__FILE__ ": chdir_section") }
102 };
103 static CRITICAL_SECTION chdir_section = { &critsect_debug, -1, 0, 0, 0, 0 };
104
105
106 /***********************************************************************
107  *           init_options
108  *
109  * Initialize the show_dir_symlinks and show_dot_files options.
110  */
111 static void init_options(void)
112 {
113     static const WCHAR WineW[] = {'M','a','c','h','i','n','e','\\',
114                                   'S','o','f','t','w','a','r','e','\\',
115                                   'W','i','n','e','\\','W','i','n','e','\\',
116                                   'C','o','n','f','i','g','\\','W','i','n','e',0};
117     static const WCHAR ShowDotFilesW[] = {'S','h','o','w','D','o','t','F','i','l','e','s',0};
118     static const WCHAR ShowDirSymlinksW[] = {'S','h','o','w','D','i','r','S','y','m','l','i','n','k','s',0};
119     char tmp[80];
120     HKEY hkey;
121     DWORD dummy;
122     OBJECT_ATTRIBUTES attr;
123     UNICODE_STRING nameW;
124
125     show_dot_files = show_dir_symlinks = 0;
126
127     attr.Length = sizeof(attr);
128     attr.RootDirectory = 0;
129     attr.ObjectName = &nameW;
130     attr.Attributes = 0;
131     attr.SecurityDescriptor = NULL;
132     attr.SecurityQualityOfService = NULL;
133     RtlInitUnicodeString( &nameW, WineW );
134
135     if (!NtOpenKey( &hkey, KEY_ALL_ACCESS, &attr ))
136     {
137         RtlInitUnicodeString( &nameW, ShowDotFilesW );
138         if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
139         {
140             WCHAR *str = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
141             show_dot_files = IS_OPTION_TRUE( str[0] );
142         }
143         RtlInitUnicodeString( &nameW, ShowDirSymlinksW );
144         if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
145         {
146             WCHAR *str = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
147             show_dir_symlinks = IS_OPTION_TRUE( str[0] );
148         }
149         NtClose( hkey );
150     }
151 }
152
153
154 /***********************************************************************
155  *           hash_short_file_name
156  *
157  * Transform a Unix file name into a hashed DOS name. If the name is a valid
158  * DOS name, it is converted to upper-case; otherwise it is replaced by a
159  * hashed version that fits in 8.3 format.
160  * 'buffer' must be at least 12 characters long.
161  * Returns length of short name in bytes; short name is NOT null-terminated.
162  */
163 static ULONG hash_short_file_name( const UNICODE_STRING *name, LPWSTR buffer )
164 {
165     static const WCHAR invalid_chars[] = { INVALID_DOS_CHARS,'~','.',0 };
166     static const char hash_chars[32] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345";
167
168     LPCWSTR p, ext, end = name->Buffer + name->Length / sizeof(WCHAR);
169     LPWSTR dst;
170     unsigned short hash;
171     int i;
172
173     /* Compute the hash code of the file name */
174     /* If you know something about hash functions, feel free to */
175     /* insert a better algorithm here... */
176     if (!is_case_sensitive)
177     {
178         for (p = name->Buffer, hash = 0xbeef; p < end - 1; p++)
179             hash = (hash<<3) ^ (hash>>5) ^ tolowerW(*p) ^ (tolowerW(p[1]) << 8);
180         hash = (hash<<3) ^ (hash>>5) ^ tolowerW(*p); /* Last character */
181     }
182     else
183     {
184         for (p = name->Buffer, hash = 0xbeef; p < end - 1; p++)
185             hash = (hash << 3) ^ (hash >> 5) ^ *p ^ (p[1] << 8);
186         hash = (hash << 3) ^ (hash >> 5) ^ *p;  /* Last character */
187     }
188
189     /* Find last dot for start of the extension */
190     for (p = name->Buffer + 1, ext = NULL; p < end - 1; p++) if (*p == '.') ext = p;
191
192     /* Copy first 4 chars, replacing invalid chars with '_' */
193     for (i = 4, p = name->Buffer, dst = buffer; i > 0; i--, p++)
194     {
195         if (p == end || p == ext) break;
196         *dst++ = strchrW( invalid_chars, *p ) ? '_' : toupperW(*p);
197     }
198     /* Pad to 5 chars with '~' */
199     while (i-- >= 0) *dst++ = '~';
200
201     /* Insert hash code converted to 3 ASCII chars */
202     *dst++ = hash_chars[(hash >> 10) & 0x1f];
203     *dst++ = hash_chars[(hash >> 5) & 0x1f];
204     *dst++ = hash_chars[hash & 0x1f];
205
206     /* Copy the first 3 chars of the extension (if any) */
207     if (ext)
208     {
209         *dst++ = '.';
210         for (i = 3, ext++; (i > 0) && ext < end; i--, ext++)
211             *dst++ = strchrW( invalid_chars, *ext ) ? '_' : toupperW(*ext);
212     }
213     return dst - buffer;
214 }
215
216
217 /***********************************************************************
218  *           match_filename
219  *
220  * Check a long file name against a mask.
221  *
222  * Tests (done in W95 DOS shell - case insensitive):
223  * *.txt                        test1.test.txt                          *
224  * *st1*                        test1.txt                               *
225  * *.t??????.t*                 test1.ta.tornado.txt                    *
226  * *tornado*                    test1.ta.tornado.txt                    *
227  * t*t                          test1.ta.tornado.txt                    *
228  * ?est*                        test1.txt                               *
229  * ?est???                      test1.txt                               -
230  * *test1.txt*                  test1.txt                               *
231  * h?l?o*t.dat                  hellothisisatest.dat                    *
232  */
233 static BOOLEAN match_filename( const UNICODE_STRING *name_str, const UNICODE_STRING *mask_str )
234 {
235     int mismatch;
236     const WCHAR *name = name_str->Buffer;
237     const WCHAR *mask = mask_str->Buffer;
238     const WCHAR *name_end = name + name_str->Length / sizeof(WCHAR);
239     const WCHAR *mask_end = mask + mask_str->Length / sizeof(WCHAR);
240     const WCHAR *lastjoker = NULL;
241     const WCHAR *next_to_retry = NULL;
242
243     TRACE("(%s, %s)\n", debugstr_us(name_str), debugstr_us(mask_str));
244
245     while (name < name_end && mask < mask_end)
246     {
247         switch(*mask)
248         {
249         case '*':
250             mask++;
251             while (mask < mask_end && *mask == '*') mask++;  /* Skip consecutive '*' */
252             if (mask == mask_end) return TRUE; /* end of mask is all '*', so match */
253             lastjoker = mask;
254
255             /* skip to the next match after the joker(s) */
256             if (is_case_sensitive)
257                 while (name < name_end && (*name != *mask)) name++;
258             else
259                 while (name < name_end && (toupperW(*name) != toupperW(*mask))) name++;
260             next_to_retry = name;
261             break;
262         case '?':
263             mask++;
264             name++;
265             break;
266         default:
267             if (is_case_sensitive) mismatch = (*mask != *name);
268             else mismatch = (toupperW(*mask) != toupperW(*name));
269
270             if (!mismatch)
271             {
272                 mask++;
273                 name++;
274                 if (mask == mask_end)
275                 {
276                     if (name == name_end) return TRUE;
277                     if (lastjoker) mask = lastjoker;
278                 }
279             }
280             else /* mismatch ! */
281             {
282                 if (lastjoker) /* we had an '*', so we can try unlimitedly */
283                 {
284                     mask = lastjoker;
285
286                     /* this scan sequence was a mismatch, so restart
287                      * 1 char after the first char we checked last time */
288                     next_to_retry++;
289                     name = next_to_retry;
290                 }
291                 else return FALSE; /* bad luck */
292             }
293             break;
294         }
295     }
296     while (mask < mask_end && ((*mask == '.') || (*mask == '*')))
297         mask++;  /* Ignore trailing '.' or '*' in mask */
298     return (name == name_end && mask == mask_end);
299 }
300
301
302 /***********************************************************************
303  *           append_entry
304  *
305  * helper for NtQueryDirectoryFile
306  */
307 static FILE_BOTH_DIR_INFORMATION *append_entry( void *info_ptr, ULONG *pos, ULONG max_length,
308                                                 const char *long_name, const char *short_name,
309                                                 const UNICODE_STRING *mask )
310 {
311     FILE_BOTH_DIR_INFORMATION *info;
312     int i, long_len, short_len, total_len;
313     struct stat st;
314     WCHAR long_nameW[MAX_DIR_ENTRY_LEN];
315     WCHAR short_nameW[12];
316     UNICODE_STRING str;
317
318     long_len = ntdll_umbstowcs( 0, long_name, strlen(long_name), long_nameW, MAX_DIR_ENTRY_LEN );
319     if (long_len == -1) return NULL;
320
321     str.Buffer = long_nameW;
322     str.Length = long_len * sizeof(WCHAR);
323     str.MaximumLength = sizeof(long_nameW);
324
325     if (short_name)
326     {
327         short_len = ntdll_umbstowcs( 0, short_name, strlen(short_name),
328                                      short_nameW, sizeof(short_nameW) / sizeof(WCHAR) );
329         if (short_len == -1) short_len = sizeof(short_nameW) / sizeof(WCHAR);
330     }
331     else  /* generate a short name if necessary */
332     {
333         BOOLEAN spaces;
334
335         short_len = 0;
336         if (!RtlIsNameLegalDOS8Dot3( &str, NULL, &spaces ) || spaces)
337             short_len = hash_short_file_name( &str, short_nameW );
338     }
339
340     TRACE( "long %s short %s mask %s\n",
341            debugstr_us(&str), debugstr_wn(short_nameW, short_len), debugstr_us(mask) );
342
343     if (mask && !match_filename( &str, mask ))
344     {
345         if (!short_len) return NULL;  /* no short name to match */
346         str.Buffer = short_nameW;
347         str.Length = short_len * sizeof(WCHAR);
348         str.MaximumLength = sizeof(short_nameW);
349         if (!match_filename( &str, mask )) return NULL;
350     }
351
352     total_len = (sizeof(*info) - sizeof(info->FileName) + long_len*sizeof(WCHAR) + 3) & ~3;
353     info = (FILE_BOTH_DIR_INFORMATION *)((char *)info_ptr + *pos);
354
355     if (*pos + total_len > max_length) total_len = max_length - *pos;
356
357     if (lstat( long_name, &st ) == -1) return NULL;
358     if (S_ISLNK( st.st_mode ))
359     {
360         if (stat( long_name, &st ) == -1) return NULL;
361         if (S_ISDIR( st.st_mode ) && !show_dir_symlinks) return NULL;
362     }
363
364     info->NextEntryOffset = total_len;
365     info->FileIndex = 0;  /* NTFS always has 0 here, so let's not bother with it */
366
367     RtlSecondsSince1970ToTime( st.st_mtime, &info->CreationTime );
368     RtlSecondsSince1970ToTime( st.st_mtime, &info->LastWriteTime );
369     RtlSecondsSince1970ToTime( st.st_atime, &info->LastAccessTime );
370     RtlSecondsSince1970ToTime( st.st_ctime, &info->ChangeTime );
371
372     if (S_ISDIR(st.st_mode))
373     {
374         info->EndOfFile.QuadPart = info->AllocationSize.QuadPart = 0;
375         info->FileAttributes = FILE_ATTRIBUTE_DIRECTORY;
376     }
377     else
378     {
379         info->EndOfFile.QuadPart = st.st_size;
380         info->AllocationSize.QuadPart = (ULONGLONG)st.st_blocks * 512;
381         info->FileAttributes = FILE_ATTRIBUTE_ARCHIVE;
382     }
383
384     if (!(st.st_mode & S_IWUSR))
385         info->FileAttributes |= FILE_ATTRIBUTE_READONLY;
386
387     if (!show_dot_files && long_name[0] == '.' && long_name[1] && (long_name[1] != '.' || long_name[2]))
388         info->FileAttributes |= FILE_ATTRIBUTE_HIDDEN;
389
390     info->EaSize = 0; /* FIXME */
391     info->ShortNameLength = short_len * sizeof(WCHAR);
392     for (i = 0; i < short_len; i++) info->ShortName[i] = toupperW(short_nameW[i]);
393     info->FileNameLength = long_len * sizeof(WCHAR);
394     memcpy( info->FileName, long_nameW,
395             min( info->FileNameLength, total_len-sizeof(*info)+sizeof(info->FileName) ));
396
397     *pos += total_len;
398     return info;
399 }
400
401
402 /******************************************************************************
403  *  NtQueryDirectoryFile        [NTDLL.@]
404  *  ZwQueryDirectoryFile        [NTDLL.@]
405  */
406 NTSTATUS WINAPI NtQueryDirectoryFile( HANDLE handle, HANDLE event,
407                                       PIO_APC_ROUTINE apc_routine, PVOID apc_context,
408                                       PIO_STATUS_BLOCK io,
409                                       PVOID buffer, ULONG length,
410                                       FILE_INFORMATION_CLASS info_class,
411                                       BOOLEAN single_entry,
412                                       PUNICODE_STRING mask,
413                                       BOOLEAN restart_scan )
414 {
415     int cwd, fd;
416     FILE_BOTH_DIR_INFORMATION *info, *last_info = NULL;
417     static const int max_dir_info_size = sizeof(*info) + (MAX_DIR_ENTRY_LEN-1) * sizeof(WCHAR);
418
419     TRACE("(%p %p %p %p %p %p 0x%08lx 0x%08x 0x%08x %s 0x%08x\n",
420           handle, event, apc_routine, apc_context, io, buffer,
421           length, info_class, single_entry, debugstr_us(mask),
422           restart_scan);
423
424     if (length < sizeof(*info)) return STATUS_INFO_LENGTH_MISMATCH;
425
426     if (event || apc_routine)
427     {
428         FIXME( "Unsupported yet option\n" );
429         return io->u.Status = STATUS_NOT_IMPLEMENTED;
430     }
431     if (info_class != FileBothDirectoryInformation)
432     {
433         FIXME( "Unsupported file info class %d\n", info_class );
434         return io->u.Status = STATUS_NOT_IMPLEMENTED;
435     }
436
437     if ((io->u.Status = wine_server_handle_to_fd( handle, GENERIC_READ,
438                                                   &fd, NULL, NULL )) != STATUS_SUCCESS)
439         return io->u.Status;
440
441     io->Information = 0;
442
443     RtlEnterCriticalSection( &chdir_section );
444
445     if (show_dir_symlinks == -1) init_options();
446
447     if ((cwd = open(".", O_RDONLY)) != -1 && fchdir( fd ) != -1)
448     {
449         off_t old_pos = 0;
450
451 #ifdef VFAT_IOCTL_READDIR_BOTH
452         KERNEL_DIRENT de[2];
453
454         io->u.Status = STATUS_SUCCESS;
455
456         /* Check if the VFAT ioctl is supported on this directory */
457
458         if (restart_scan) lseek( fd, 0, SEEK_SET );
459         else old_pos = lseek( fd, 0, SEEK_CUR );
460
461         if (ioctl( fd, VFAT_IOCTL_READDIR_BOTH, (long)de ) != -1)
462         {
463             if (length < max_dir_info_size)  /* we may have to return a partial entry here */
464             {
465                 for (;;)
466                 {
467                     if (!de[0].d_reclen) break;
468                     if (de[1].d_name[0])
469                         info = append_entry( buffer, &io->Information, length,
470                                              de[1].d_name, de[0].d_name, mask );
471                     else
472                         info = append_entry( buffer, &io->Information, length,
473                                              de[0].d_name, NULL, mask );
474                     if (info)
475                     {
476                         last_info = info;
477                         if ((char *)info->FileName + info->FileNameLength > (char *)buffer + length)
478                         {
479                             io->u.Status = STATUS_BUFFER_OVERFLOW;
480                             lseek( fd, old_pos, SEEK_SET );  /* restore pos to previous entry */
481                         }
482                         break;
483                     }
484                     old_pos = lseek( fd, 0, SEEK_CUR );
485                     if (ioctl( fd, VFAT_IOCTL_READDIR_BOTH, (long)de ) == -1) break;
486                 }
487             }
488             else  /* we'll only return full entries, no need to worry about overflow */
489             {
490                 for (;;)
491                 {
492                     if (!de[0].d_reclen) break;
493                     if (de[1].d_name[0])
494                         info = append_entry( buffer, &io->Information, length,
495                                              de[1].d_name, de[0].d_name, mask );
496                     else
497                         info = append_entry( buffer, &io->Information, length,
498                                              de[0].d_name, NULL, mask );
499                     if (info)
500                     {
501                         last_info = info;
502                         if (single_entry) break;
503                         /* check if we still have enough space for the largest possible entry */
504                         if (io->Information + max_dir_info_size > length) break;
505                     }
506                     if (ioctl( fd, VFAT_IOCTL_READDIR_BOTH, (long)de ) == -1) break;
507                 }
508             }
509         }
510         else if (errno != ENOENT)
511 #endif  /* VFAT_IOCTL_READDIR_BOTH */
512         {
513             DIR *dir;
514             struct dirent *de;
515
516             if (!(dir = opendir( "." )))
517             {
518                 io->u.Status = FILE_GetNtStatus();
519                 goto done;
520             }
521             if (!restart_scan)
522             {
523                 old_pos = lseek( fd, 0, SEEK_CUR );
524                 seekdir( dir, old_pos );
525             }
526             io->u.Status = STATUS_SUCCESS;
527
528             if (length < max_dir_info_size)  /* we may have to return a partial entry here */
529             {
530                 while ((de = readdir( dir )))
531                 {
532                     info = append_entry( buffer, &io->Information, length,
533                                          de->d_name, NULL, mask );
534                     if (info)
535                     {
536                         last_info = info;
537                         if ((char *)info->FileName + info->FileNameLength > (char *)buffer + length)
538                             io->u.Status = STATUS_BUFFER_OVERFLOW;
539                         else
540                             old_pos = telldir( dir );
541                         break;
542                     }
543                     old_pos = telldir( dir );
544                 }
545             }
546             else  /* we'll only return full entries, no need to worry about overflow */
547             {
548                 while ((de = readdir( dir )))
549                 {
550                     info = append_entry( buffer, &io->Information, length,
551                                          de->d_name, NULL, mask );
552                     if (info)
553                     {
554                         last_info = info;
555                         if (single_entry) break;
556                         /* check if we still have enough space for the largest possible entry */
557                         if (io->Information + max_dir_info_size > length) break;
558                     }
559                 }
560                 old_pos = telldir( dir );
561             }
562             lseek( fd, old_pos, SEEK_SET );  /* store dir offset as filepos for fd */
563             closedir( dir );
564         }
565
566         if (last_info) last_info->NextEntryOffset = 0;
567         else io->u.Status = restart_scan ? STATUS_NO_SUCH_FILE : STATUS_NO_MORE_FILES;
568
569     done:
570         if (fchdir( cwd ) == -1) chdir( "/" );
571     }
572     else io->u.Status = FILE_GetNtStatus();
573
574     RtlLeaveCriticalSection( &chdir_section );
575
576     wine_server_release_fd( handle, fd );
577     if (cwd != -1) close( cwd );
578     TRACE( "=> %lx (%ld)\n", io->u.Status, io->Information );
579     return io->u.Status;
580 }
581
582
583 /***********************************************************************
584  *           find_file_in_dir
585  *
586  * Find a file in a directory the hard way, by doing a case-insensitive search.
587  * The file found is appended to unix_name at pos.
588  * There must be at least MAX_DIR_ENTRY_LEN+2 chars available at pos.
589  */
590 static NTSTATUS find_file_in_dir( char *unix_name, int pos, const WCHAR *name, int length,
591                                   int is_last, int check_last, int check_case )
592 {
593     WCHAR buffer[MAX_DIR_ENTRY_LEN];
594     UNICODE_STRING str;
595     BOOLEAN spaces;
596     DIR *dir;
597     struct dirent *de;
598     struct stat st;
599     int ret, used_default, is_name_8_dot_3;
600
601     /* try a shortcut for this directory */
602
603     unix_name[pos++] = '/';
604     ret = ntdll_wcstoumbs( 0, name, length, unix_name + pos, MAX_DIR_ENTRY_LEN,
605                            NULL, &used_default );
606     /* if we used the default char, the Unix name won't round trip properly back to Unicode */
607     /* so it cannot match the file we are looking for */
608     if (ret >= 0 && !used_default)
609     {
610         unix_name[pos + ret] = 0;
611         if (!stat( unix_name, &st )) return STATUS_SUCCESS;
612     }
613     if (check_case) goto not_found;  /* we want an exact match */
614
615     if (pos > 1) unix_name[pos - 1] = 0;
616     else unix_name[1] = 0;  /* keep the initial slash */
617
618     /* check if it fits in 8.3 so that we don't look for short names if we won't need them */
619
620     str.Buffer = (WCHAR *)name;
621     str.Length = length * sizeof(WCHAR);
622     str.MaximumLength = str.Length;
623     is_name_8_dot_3 = RtlIsNameLegalDOS8Dot3( &str, NULL, &spaces ) && !spaces;
624
625     /* now look for it through the directory */
626
627 #ifdef VFAT_IOCTL_READDIR_BOTH
628     if (is_name_8_dot_3)
629     {
630         int fd = open( unix_name, O_RDONLY | O_DIRECTORY );
631         if (fd != -1)
632         {
633             KERNEL_DIRENT de[2];
634
635             if (ioctl( fd, VFAT_IOCTL_READDIR_BOTH, (long)de ) != -1)
636             {
637                 unix_name[pos - 1] = '/';
638                 for (;;)
639                 {
640                     if (!de[0].d_reclen) break;
641
642                     if (de[1].d_name[0])
643                     {
644                         ret = ntdll_umbstowcs( 0, de[1].d_name, strlen(de[1].d_name),
645                                                buffer, MAX_DIR_ENTRY_LEN );
646                         if (ret == length && !memicmpW( buffer, name, length))
647                         {
648                             strcpy( unix_name + pos, de[1].d_name );
649                             close( fd );
650                             return STATUS_SUCCESS;
651                         }
652                     }
653                     ret = ntdll_umbstowcs( 0, de[0].d_name, strlen(de[0].d_name),
654                                            buffer, MAX_DIR_ENTRY_LEN );
655                     if (ret == length && !memicmpW( buffer, name, length))
656                     {
657                         strcpy( unix_name + pos,
658                                 de[1].d_name[0] ? de[1].d_name : de[0].d_name );
659                         close( fd );
660                         return STATUS_SUCCESS;
661                     }
662                     if (ioctl( fd, VFAT_IOCTL_READDIR_BOTH, (long)de ) == -1)
663                     {
664                         close( fd );
665                         goto not_found;
666                     }
667                 }
668             }
669             close( fd );
670         }
671         /* fall through to normal handling */
672     }
673 #endif /* VFAT_IOCTL_READDIR_BOTH */
674
675     if (!(dir = opendir( unix_name ))) return FILE_GetNtStatus();
676     unix_name[pos - 1] = '/';
677     str.Buffer = buffer;
678     str.MaximumLength = sizeof(buffer);
679     while ((de = readdir( dir )))
680     {
681         ret = ntdll_umbstowcs( 0, de->d_name, strlen(de->d_name), buffer, MAX_DIR_ENTRY_LEN );
682         if (ret == length && !memicmpW( buffer, name, length ))
683         {
684             strcpy( unix_name + pos, de->d_name );
685             closedir( dir );
686             return STATUS_SUCCESS;
687         }
688
689         if (!is_name_8_dot_3) continue;
690
691         str.Length = ret * sizeof(WCHAR);
692         if (!RtlIsNameLegalDOS8Dot3( &str, NULL, &spaces ) || spaces)
693         {
694             WCHAR short_nameW[12];
695             ret = hash_short_file_name( &str, short_nameW );
696             if (ret == length && !memicmpW( short_nameW, name, length ))
697             {
698                 strcpy( unix_name + pos, de->d_name );
699                 closedir( dir );
700                 return STATUS_SUCCESS;
701             }
702         }
703     }
704     closedir( dir );
705     goto not_found;  /* avoid warning */
706
707 not_found:
708     if (is_last && !check_last)  /* return the name anyway */
709     {
710         int used_default;
711         ret = ntdll_wcstoumbs( 0, name, length, unix_name + pos,
712                                MAX_DIR_ENTRY_LEN, NULL, &used_default );
713         if (ret > 0 && !used_default)
714         {
715             unix_name[pos + ret] = 0;
716             return STATUS_SUCCESS;
717         }
718     }
719     unix_name[pos - 1] = 0;
720     return is_last ? STATUS_NO_SUCH_FILE : STATUS_OBJECT_PATH_NOT_FOUND;
721 }
722
723
724 /* return the length of the DOS namespace prefix if any */
725 static inline int get_dos_prefix_len( const UNICODE_STRING *name )
726 {
727     static const WCHAR nt_prefixW[] = {'\\','?','?','\\'};
728     static const WCHAR dosdev_prefixW[] = {'\\','D','o','s','D','e','v','i','c','e','s','\\'};
729
730     if (name->Length > sizeof(nt_prefixW) &&
731         !memcmp( name->Buffer, nt_prefixW, sizeof(nt_prefixW) ))
732         return sizeof(nt_prefixW) / sizeof(WCHAR);
733
734     if (name->Length > sizeof(dosdev_prefixW) &&
735         !memicmpW( name->Buffer, dosdev_prefixW, sizeof(dosdev_prefixW)/sizeof(WCHAR) ))
736         return sizeof(dosdev_prefixW) / sizeof(WCHAR);
737
738     return 0;
739 }
740
741
742 /******************************************************************************
743  *           DIR_nt_to_unix
744  *
745  * Convert a file name from NT namespace to Unix namespace.
746  */
747 NTSTATUS DIR_nt_to_unix( const UNICODE_STRING *nameW, ANSI_STRING *unix_name_ret,
748                          int check_last, int check_case )
749 {
750     NTSTATUS status = STATUS_NO_SUCH_FILE;
751     const char *config_dir = wine_get_config_dir();
752     const WCHAR *end, *name;
753     struct stat st;
754     char *unix_name;
755     int pos, ret, name_len, unix_len, used_default;
756
757     name     = nameW->Buffer;
758     name_len = nameW->Length / sizeof(WCHAR);
759
760     if ((pos = get_dos_prefix_len( nameW )))
761     {
762         name += pos;
763         name_len -= pos;
764         if (name_len < 3 || !isalphaW(name[0]) || name[1] != ':' || !IS_SEPARATOR(name[2]))
765             return STATUS_NO_SUCH_FILE;
766         name += 2;  /* skip drive letter */
767         name_len -= 2;
768         unix_len = ntdll_wcstoumbs( 0, name, name_len, NULL, 0, NULL, NULL );
769         unix_len += MAX_DIR_ENTRY_LEN + 3;
770         unix_len += strlen(config_dir) + sizeof("/dosdevices/a:");
771         if (!(unix_name = RtlAllocateHeap( GetProcessHeap(), 0, unix_len )))
772             return STATUS_NO_MEMORY;
773         strcpy( unix_name, config_dir );
774         strcat( unix_name, "/dosdevices/a:" );
775         pos = strlen(unix_name);
776         unix_name[pos - 2] = tolowerW( name[-2] );
777     }
778     else  /* no DOS prefix, assume NT native name, map directly to Unix */
779     {
780         if (!name_len || !IS_SEPARATOR(name[0])) return STATUS_NO_SUCH_FILE;
781         unix_len = ntdll_wcstoumbs( 0, name, name_len, NULL, 0, NULL, NULL );
782         unix_len += MAX_DIR_ENTRY_LEN + 3;
783         if (!(unix_name = RtlAllocateHeap( GetProcessHeap(), 0, unix_len )))
784             return STATUS_NO_MEMORY;
785         pos = 0;
786     }
787
788     /* try a shortcut first */
789
790     ret = ntdll_wcstoumbs( 0, name, name_len, unix_name + pos, unix_len - pos - 1,
791                            NULL, &used_default );
792     if (ret > 0 && !used_default)  /* if we used the default char the name didn't convert properly */
793     {
794         char *p;
795         unix_name[pos + ret] = 0;
796         for (p = unix_name + pos ; *p; p++) if (*p == '\\') *p = '/';
797         if (!stat( unix_name, &st )) goto done;
798     }
799     if (check_case && check_last)
800     {
801         RtlFreeHeap( GetProcessHeap(), 0, unix_name );
802         return STATUS_NO_SUCH_FILE;
803     }
804
805     /* now do it component by component */
806
807     for (;;)
808     {
809         while (name_len && IS_SEPARATOR(*name))
810         {
811             name++;
812             name_len--;
813         }
814         if (!name_len) break;
815
816         end = name;
817         while (end < name + name_len && !IS_SEPARATOR(*end)) end++;
818
819         /* grow the buffer if needed */
820
821         if (unix_len - pos < MAX_DIR_ENTRY_LEN + 2)
822         {
823             char *new_name;
824             unix_len += 2 * MAX_DIR_ENTRY_LEN;
825             if (!(new_name = RtlReAllocateHeap( GetProcessHeap(), 0, unix_name, unix_len )))
826             {
827                 RtlFreeHeap( GetProcessHeap(), 0, unix_name );
828                 return STATUS_NO_MEMORY;
829             }
830             unix_name = new_name;
831         }
832
833         status = find_file_in_dir( unix_name, pos, name, end - name,
834                                    (end - name == name_len), check_last, check_case );
835         if (status != STATUS_SUCCESS)
836         {
837             /* couldn't find it at all, fail */
838             WARN( "%s not found in %s\n", debugstr_w(name), unix_name );
839             RtlFreeHeap( GetProcessHeap(), 0, unix_name );
840             return status;
841         }
842
843         pos += strlen( unix_name + pos );
844         name_len -= end - name;
845         name = end;
846     }
847
848     WARN( "%s -> %s required a case-insensitive search\n",
849           debugstr_us(nameW), debugstr_a(unix_name) );
850
851 done:
852     TRACE( "%s -> %s\n", debugstr_us(nameW), debugstr_a(unix_name) );
853     unix_name_ret->Buffer = unix_name;
854     unix_name_ret->Length = strlen(unix_name);
855     unix_name_ret->MaximumLength = unix_len;
856     return STATUS_SUCCESS;
857 }
858
859
860 /******************************************************************
861  *              RtlDoesFileExists_U   (NTDLL.@)
862  */
863 BOOLEAN WINAPI RtlDoesFileExists_U(LPCWSTR file_name)
864 {
865     UNICODE_STRING nt_name;
866     ANSI_STRING unix_name;
867     BOOLEAN ret;
868
869     if (!RtlDosPathNameToNtPathName_U( file_name, &nt_name, NULL, NULL )) return FALSE;
870     ret = (DIR_nt_to_unix( &nt_name, &unix_name, TRUE, FALSE ) == STATUS_SUCCESS);
871     if (ret) RtlFreeAnsiString( &unix_name );
872     RtlFreeUnicodeString( &nt_name );
873     return ret;
874 }