2 * NTDLL directory functions
4 * Copyright 1993 Erik Bos
5 * Copyright 2003 Eric Pouech
6 * Copyright 1996, 2004 Alexandre Julliard
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.
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.
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
24 #include "wine/port.h"
26 #include <sys/types.h>
34 #ifdef HAVE_SYS_IOCTL_H
35 #include <sys/ioctl.h>
37 #ifdef HAVE_LINUX_IOCTL_H
38 #include <linux/ioctl.h>
45 #define NONAMELESSUNION
46 #define NONAMELESSSTRUCT
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"
59 WINE_DEFAULT_DEBUG_CHANNEL(file);
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? */
64 /* We want the real kernel dirent structure, not the libc one */
69 unsigned short d_reclen;
73 #define VFAT_IOCTL_READDIR_BOTH _IOR('r', 1, KERNEL_DIRENT [2] )
76 # define O_DIRECTORY 0200000 /* must be directory */
80 #undef VFAT_IOCTL_READDIR_BOTH /* just in case... */
83 #define IS_OPTION_TRUE(ch) ((ch) == 'y' || (ch) == 'Y' || (ch) == 't' || (ch) == 'T' || (ch) == '1')
84 #define IS_SEPARATOR(ch) ((ch) == '\\' || (ch) == '/')
86 #define INVALID_DOS_CHARS '*','?','<','>','|','"','+','=',',',';','[',']',' ','\345'
88 #define MAX_DIR_ENTRY_LEN 255 /* max length of a directory entry in chars */
90 static int show_dir_symlinks = -1;
91 static int show_dot_files;
93 /* at some point we may want to allow Winelib apps to set this */
94 static const int is_case_sensitive = FALSE;
96 static CRITICAL_SECTION chdir_section;
97 static CRITICAL_SECTION_DEBUG critsect_debug =
100 { &critsect_debug.ProcessLocksList, &critsect_debug.ProcessLocksList },
101 0, 0, { 0, (DWORD)(__FILE__ ": chdir_section") }
103 static CRITICAL_SECTION chdir_section = { &critsect_debug, -1, 0, 0, 0, 0 };
106 /***********************************************************************
109 * Initialize the show_dir_symlinks and show_dot_files options.
111 static void init_options(void)
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};
122 OBJECT_ATTRIBUTES attr;
123 UNICODE_STRING nameW;
125 show_dot_files = show_dir_symlinks = 0;
127 attr.Length = sizeof(attr);
128 attr.RootDirectory = 0;
129 attr.ObjectName = &nameW;
131 attr.SecurityDescriptor = NULL;
132 attr.SecurityQualityOfService = NULL;
133 RtlInitUnicodeString( &nameW, WineW );
135 if (!NtOpenKey( &hkey, KEY_ALL_ACCESS, &attr ))
137 RtlInitUnicodeString( &nameW, ShowDotFilesW );
138 if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
140 WCHAR *str = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
141 show_dot_files = IS_OPTION_TRUE( str[0] );
143 RtlInitUnicodeString( &nameW, ShowDirSymlinksW );
144 if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
146 WCHAR *str = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
147 show_dir_symlinks = IS_OPTION_TRUE( str[0] );
154 /***********************************************************************
155 * hash_short_file_name
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.
163 static ULONG hash_short_file_name( const UNICODE_STRING *name, LPWSTR buffer )
165 static const WCHAR invalid_chars[] = { INVALID_DOS_CHARS,'~','.',0 };
166 static const char hash_chars[32] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345";
168 LPCWSTR p, ext, end = name->Buffer + name->Length / sizeof(WCHAR);
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)
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 */
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 */
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;
192 /* Copy first 4 chars, replacing invalid chars with '_' */
193 for (i = 4, p = name->Buffer, dst = buffer; i > 0; i--, p++)
195 if (p == end || p == ext) break;
196 *dst++ = strchrW( invalid_chars, *p ) ? '_' : toupperW(*p);
198 /* Pad to 5 chars with '~' */
199 while (i-- >= 0) *dst++ = '~';
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];
206 /* Copy the first 3 chars of the extension (if any) */
210 for (i = 3, ext++; (i > 0) && ext < end; i--, ext++)
211 *dst++ = strchrW( invalid_chars, *ext ) ? '_' : toupperW(*ext);
217 /***********************************************************************
220 * Check a long file name against a mask.
222 * Tests (done in W95 DOS shell - case insensitive):
223 * *.txt test1.test.txt *
225 * *.t??????.t* test1.ta.tornado.txt *
226 * *tornado* test1.ta.tornado.txt *
227 * t*t test1.ta.tornado.txt *
229 * ?est??? test1.txt -
230 * *test1.txt* test1.txt *
231 * h?l?o*t.dat hellothisisatest.dat *
233 static BOOLEAN match_filename( const UNICODE_STRING *name_str, const UNICODE_STRING *mask_str )
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;
243 TRACE("(%s, %s)\n", debugstr_us(name_str), debugstr_us(mask_str));
245 while (name < name_end && mask < mask_end)
251 while (mask < mask_end && *mask == '*') mask++; /* Skip consecutive '*' */
252 if (mask == mask_end) return TRUE; /* end of mask is all '*', so match */
255 /* skip to the next match after the joker(s) */
256 if (is_case_sensitive)
257 while (name < name_end && (*name != *mask)) name++;
259 while (name < name_end && (toupperW(*name) != toupperW(*mask))) name++;
260 next_to_retry = name;
267 if (is_case_sensitive) mismatch = (*mask != *name);
268 else mismatch = (toupperW(*mask) != toupperW(*name));
274 if (mask == mask_end)
276 if (name == name_end) return TRUE;
277 if (lastjoker) mask = lastjoker;
280 else /* mismatch ! */
282 if (lastjoker) /* we had an '*', so we can try unlimitedly */
286 /* this scan sequence was a mismatch, so restart
287 * 1 char after the first char we checked last time */
289 name = next_to_retry;
291 else return FALSE; /* bad luck */
296 while (mask < mask_end && ((*mask == '.') || (*mask == '*')))
297 mask++; /* Ignore trailing '.' or '*' in mask */
298 return (name == name_end && mask == mask_end);
302 /***********************************************************************
305 * helper for NtQueryDirectoryFile
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 )
311 FILE_BOTH_DIR_INFORMATION *info;
312 int i, long_len, short_len, total_len;
314 WCHAR long_nameW[MAX_DIR_ENTRY_LEN];
315 WCHAR short_nameW[12];
318 long_len = ntdll_umbstowcs( 0, long_name, strlen(long_name), long_nameW, MAX_DIR_ENTRY_LEN );
319 if (long_len == -1) return NULL;
321 str.Buffer = long_nameW;
322 str.Length = long_len * sizeof(WCHAR);
323 str.MaximumLength = sizeof(long_nameW);
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);
331 else /* generate a short name if necessary */
336 if (!RtlIsNameLegalDOS8Dot3( &str, NULL, &spaces ) || spaces)
337 short_len = hash_short_file_name( &str, short_nameW );
340 TRACE( "long %s short %s mask %s\n",
341 debugstr_us(&str), debugstr_wn(short_nameW, short_len), debugstr_us(mask) );
343 if (mask && !match_filename( &str, mask ))
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;
352 total_len = (sizeof(*info) - sizeof(info->FileName) + long_len*sizeof(WCHAR) + 3) & ~3;
353 info = (FILE_BOTH_DIR_INFORMATION *)((char *)info_ptr + *pos);
355 if (*pos + total_len > max_length) total_len = max_length - *pos;
357 if (lstat( long_name, &st ) == -1) return NULL;
358 if (S_ISLNK( st.st_mode ))
360 if (stat( long_name, &st ) == -1) return NULL;
361 if (S_ISDIR( st.st_mode ) && !show_dir_symlinks) return NULL;
364 info->NextEntryOffset = total_len;
365 info->FileIndex = 0; /* NTFS always has 0 here, so let's not bother with it */
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 );
372 if (S_ISDIR(st.st_mode))
374 info->EndOfFile.QuadPart = info->AllocationSize.QuadPart = 0;
375 info->FileAttributes = FILE_ATTRIBUTE_DIRECTORY;
379 info->EndOfFile.QuadPart = st.st_size;
380 info->AllocationSize.QuadPart = (ULONGLONG)st.st_blocks * 512;
381 info->FileAttributes = FILE_ATTRIBUTE_ARCHIVE;
384 if (!(st.st_mode & S_IWUSR))
385 info->FileAttributes |= FILE_ATTRIBUTE_READONLY;
387 if (!show_dot_files && long_name[0] == '.' && long_name[1] && (long_name[1] != '.' || long_name[2]))
388 info->FileAttributes |= FILE_ATTRIBUTE_HIDDEN;
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) ));
402 /******************************************************************************
403 * NtQueryDirectoryFile [NTDLL.@]
404 * ZwQueryDirectoryFile [NTDLL.@]
406 NTSTATUS WINAPI NtQueryDirectoryFile( HANDLE handle, HANDLE event,
407 PIO_APC_ROUTINE apc_routine, PVOID apc_context,
409 PVOID buffer, ULONG length,
410 FILE_INFORMATION_CLASS info_class,
411 BOOLEAN single_entry,
412 PUNICODE_STRING mask,
413 BOOLEAN restart_scan )
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);
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),
424 if (length < sizeof(*info)) return STATUS_INFO_LENGTH_MISMATCH;
426 if (event || apc_routine)
428 FIXME( "Unsupported yet option\n" );
429 return io->u.Status = STATUS_NOT_IMPLEMENTED;
431 if (info_class != FileBothDirectoryInformation)
433 FIXME( "Unsupported file info class %d\n", info_class );
434 return io->u.Status = STATUS_NOT_IMPLEMENTED;
437 if ((io->u.Status = wine_server_handle_to_fd( handle, GENERIC_READ,
438 &fd, NULL, NULL )) != STATUS_SUCCESS)
443 RtlEnterCriticalSection( &chdir_section );
445 if (show_dir_symlinks == -1) init_options();
447 if ((cwd = open(".", O_RDONLY)) != -1 && fchdir( fd ) != -1)
451 #ifdef VFAT_IOCTL_READDIR_BOTH
454 io->u.Status = STATUS_SUCCESS;
456 /* Check if the VFAT ioctl is supported on this directory */
458 if (restart_scan) lseek( fd, 0, SEEK_SET );
459 else old_pos = lseek( fd, 0, SEEK_CUR );
461 if (ioctl( fd, VFAT_IOCTL_READDIR_BOTH, (long)de ) != -1)
463 if (length < max_dir_info_size) /* we may have to return a partial entry here */
467 if (!de[0].d_reclen) break;
469 info = append_entry( buffer, &io->Information, length,
470 de[1].d_name, de[0].d_name, mask );
472 info = append_entry( buffer, &io->Information, length,
473 de[0].d_name, NULL, mask );
477 if ((char *)info->FileName + info->FileNameLength > (char *)buffer + length)
479 io->u.Status = STATUS_BUFFER_OVERFLOW;
480 lseek( fd, old_pos, SEEK_SET ); /* restore pos to previous entry */
484 old_pos = lseek( fd, 0, SEEK_CUR );
485 if (ioctl( fd, VFAT_IOCTL_READDIR_BOTH, (long)de ) == -1) break;
488 else /* we'll only return full entries, no need to worry about overflow */
492 if (!de[0].d_reclen) break;
494 info = append_entry( buffer, &io->Information, length,
495 de[1].d_name, de[0].d_name, mask );
497 info = append_entry( buffer, &io->Information, length,
498 de[0].d_name, NULL, mask );
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;
506 if (ioctl( fd, VFAT_IOCTL_READDIR_BOTH, (long)de ) == -1) break;
510 else if (errno != ENOENT)
511 #endif /* VFAT_IOCTL_READDIR_BOTH */
516 if (!(dir = opendir( "." )))
518 io->u.Status = FILE_GetNtStatus();
523 old_pos = lseek( fd, 0, SEEK_CUR );
524 seekdir( dir, old_pos );
526 io->u.Status = STATUS_SUCCESS;
528 if (length < max_dir_info_size) /* we may have to return a partial entry here */
530 while ((de = readdir( dir )))
532 info = append_entry( buffer, &io->Information, length,
533 de->d_name, NULL, mask );
537 if ((char *)info->FileName + info->FileNameLength > (char *)buffer + length)
538 io->u.Status = STATUS_BUFFER_OVERFLOW;
540 old_pos = telldir( dir );
543 old_pos = telldir( dir );
546 else /* we'll only return full entries, no need to worry about overflow */
548 while ((de = readdir( dir )))
550 info = append_entry( buffer, &io->Information, length,
551 de->d_name, NULL, mask );
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;
560 old_pos = telldir( dir );
562 lseek( fd, old_pos, SEEK_SET ); /* store dir offset as filepos for fd */
566 if (last_info) last_info->NextEntryOffset = 0;
567 else io->u.Status = restart_scan ? STATUS_NO_SUCH_FILE : STATUS_NO_MORE_FILES;
570 if (fchdir( cwd ) == -1) chdir( "/" );
572 else io->u.Status = FILE_GetNtStatus();
574 RtlLeaveCriticalSection( &chdir_section );
576 wine_server_release_fd( handle, fd );
577 if (cwd != -1) close( cwd );
578 TRACE( "=> %lx (%ld)\n", io->u.Status, io->Information );
583 /***********************************************************************
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.
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 )
593 WCHAR buffer[MAX_DIR_ENTRY_LEN];
599 int ret, used_default, is_name_8_dot_3;
601 /* try a shortcut for this directory */
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)
610 unix_name[pos + ret] = 0;
611 if (!stat( unix_name, &st )) return STATUS_SUCCESS;
613 if (check_case) goto not_found; /* we want an exact match */
615 if (pos > 1) unix_name[pos - 1] = 0;
616 else unix_name[1] = 0; /* keep the initial slash */
618 /* check if it fits in 8.3 so that we don't look for short names if we won't need them */
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;
625 /* now look for it through the directory */
627 #ifdef VFAT_IOCTL_READDIR_BOTH
630 int fd = open( unix_name, O_RDONLY | O_DIRECTORY );
635 if (ioctl( fd, VFAT_IOCTL_READDIR_BOTH, (long)de ) != -1)
637 unix_name[pos - 1] = '/';
640 if (!de[0].d_reclen) break;
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))
648 strcpy( unix_name + pos, de[1].d_name );
650 return STATUS_SUCCESS;
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))
657 strcpy( unix_name + pos,
658 de[1].d_name[0] ? de[1].d_name : de[0].d_name );
660 return STATUS_SUCCESS;
662 if (ioctl( fd, VFAT_IOCTL_READDIR_BOTH, (long)de ) == -1)
671 /* fall through to normal handling */
673 #endif /* VFAT_IOCTL_READDIR_BOTH */
675 if (!(dir = opendir( unix_name ))) return FILE_GetNtStatus();
676 unix_name[pos - 1] = '/';
678 str.MaximumLength = sizeof(buffer);
679 while ((de = readdir( dir )))
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 ))
684 strcpy( unix_name + pos, de->d_name );
686 return STATUS_SUCCESS;
689 if (!is_name_8_dot_3) continue;
691 str.Length = ret * sizeof(WCHAR);
692 if (!RtlIsNameLegalDOS8Dot3( &str, NULL, &spaces ) || spaces)
694 WCHAR short_nameW[12];
695 ret = hash_short_file_name( &str, short_nameW );
696 if (ret == length && !memicmpW( short_nameW, name, length ))
698 strcpy( unix_name + pos, de->d_name );
700 return STATUS_SUCCESS;
705 goto not_found; /* avoid warning */
708 if (is_last && !check_last) /* return the name anyway */
711 ret = ntdll_wcstoumbs( 0, name, length, unix_name + pos,
712 MAX_DIR_ENTRY_LEN, NULL, &used_default );
713 if (ret > 0 && !used_default)
715 unix_name[pos + ret] = 0;
716 return STATUS_SUCCESS;
719 unix_name[pos - 1] = 0;
720 return is_last ? STATUS_NO_SUCH_FILE : STATUS_OBJECT_PATH_NOT_FOUND;
724 /* return the length of the DOS namespace prefix if any */
725 static inline int get_dos_prefix_len( const UNICODE_STRING *name )
727 static const WCHAR nt_prefixW[] = {'\\','?','?','\\'};
728 static const WCHAR dosdev_prefixW[] = {'\\','D','o','s','D','e','v','i','c','e','s','\\'};
730 if (name->Length > sizeof(nt_prefixW) &&
731 !memcmp( name->Buffer, nt_prefixW, sizeof(nt_prefixW) ))
732 return sizeof(nt_prefixW) / sizeof(WCHAR);
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);
742 /******************************************************************************
745 * Convert a file name from NT namespace to Unix namespace.
747 NTSTATUS DIR_nt_to_unix( const UNICODE_STRING *nameW, ANSI_STRING *unix_name_ret,
748 int check_last, int check_case )
750 NTSTATUS status = STATUS_NO_SUCH_FILE;
751 const char *config_dir = wine_get_config_dir();
752 const WCHAR *end, *name;
755 int pos, ret, name_len, unix_len, used_default;
757 name = nameW->Buffer;
758 name_len = nameW->Length / sizeof(WCHAR);
760 if ((pos = get_dos_prefix_len( nameW )))
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 */
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] );
778 else /* no DOS prefix, assume NT native name, map directly to Unix */
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;
788 /* try a shortcut first */
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 */
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;
799 if (check_case && check_last)
801 RtlFreeHeap( GetProcessHeap(), 0, unix_name );
802 return STATUS_NO_SUCH_FILE;
805 /* now do it component by component */
809 while (name_len && IS_SEPARATOR(*name))
814 if (!name_len) break;
817 while (end < name + name_len && !IS_SEPARATOR(*end)) end++;
819 /* grow the buffer if needed */
821 if (unix_len - pos < MAX_DIR_ENTRY_LEN + 2)
824 unix_len += 2 * MAX_DIR_ENTRY_LEN;
825 if (!(new_name = RtlReAllocateHeap( GetProcessHeap(), 0, unix_name, unix_len )))
827 RtlFreeHeap( GetProcessHeap(), 0, unix_name );
828 return STATUS_NO_MEMORY;
830 unix_name = new_name;
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)
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 );
843 pos += strlen( unix_name + pos );
844 name_len -= end - name;
848 WARN( "%s -> %s required a case-insensitive search\n",
849 debugstr_us(nameW), debugstr_a(unix_name) );
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;
860 /******************************************************************
861 * RtlDoesFileExists_U (NTDLL.@)
863 BOOLEAN WINAPI RtlDoesFileExists_U(LPCWSTR file_name)
865 UNICODE_STRING nt_name;
866 ANSI_STRING unix_name;
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 );