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