Fixed a number of NT status values to be closer to NT behavior.
[wine/dcerpc.git] / dlls / ntdll / directory.c
blob09d3f213b08d75945b25e9bf2a7ee531a2d524dc
1 /*
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
23 #include "config.h"
24 #include "wine/port.h"
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
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"
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? */
63 #ifdef linux
64 /* We want the real kernel dirent structure, not the libc one */
65 typedef struct
67 long d_ino;
68 long d_off;
69 unsigned short d_reclen;
70 char d_name[256];
71 } KERNEL_DIRENT;
73 #define VFAT_IOCTL_READDIR_BOTH _IOR('r', 1, KERNEL_DIRENT [2] )
75 #ifndef O_DIRECTORY
76 # define O_DIRECTORY 0200000 /* must be directory */
77 #endif
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
85 #else /* linux */
86 #undef VFAT_IOCTL_READDIR_BOTH /* just in case... */
87 #undef USE_SEEKDIR
88 #endif /* linux */
90 #define IS_OPTION_TRUE(ch) ((ch) == 'y' || (ch) == 'Y' || (ch) == 't' || (ch) == 'T' || (ch) == '1')
91 #define IS_SEPARATOR(ch) ((ch) == '\\' || (ch) == '/')
93 #define INVALID_NT_CHARS '*','?','<','>','|','"'
94 #define INVALID_DOS_CHARS INVALID_NT_CHARS,'+','=',',',';','[',']',' ','\345'
96 #define MAX_DIR_ENTRY_LEN 255 /* max length of a directory entry in chars */
98 static int show_dir_symlinks = -1;
99 static int show_dot_files;
101 /* at some point we may want to allow Winelib apps to set this */
102 static const int is_case_sensitive = FALSE;
104 static CRITICAL_SECTION chdir_section;
105 static CRITICAL_SECTION_DEBUG critsect_debug =
107 0, 0, &chdir_section,
108 { &critsect_debug.ProcessLocksList, &critsect_debug.ProcessLocksList },
109 0, 0, { 0, (DWORD)(__FILE__ ": chdir_section") }
111 static CRITICAL_SECTION chdir_section = { &critsect_debug, -1, 0, 0, 0, 0 };
114 /***********************************************************************
115 * seekdir_wrapper
117 * Wrapper for supporting seekdir across multiple directory objects.
119 static inline void seekdir_wrapper( DIR *dir, off_t pos )
121 #ifdef USE_SEEKDIR
122 seekdir( dir, pos );
123 #else
124 while (pos-- > 0) if (!readdir( dir )) break;
125 #endif
128 /***********************************************************************
129 * telldir_wrapper
131 * Wrapper for supporting telldir across multiple directory objects.
133 static inline off_t telldir_wrapper( DIR *dir, off_t pos, int count )
135 #ifdef USE_SEEKDIR
136 return telldir( dir );
137 #else
138 return pos + count;
139 #endif
143 /***********************************************************************
144 * init_options
146 * Initialize the show_dir_symlinks and show_dot_files options.
148 static void init_options(void)
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;
162 show_dot_files = show_dir_symlinks = 0;
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 );
172 if (!NtOpenKey( &hkey, KEY_ALL_ACCESS, &attr ))
174 RtlInitUnicodeString( &nameW, ShowDotFilesW );
175 if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
177 WCHAR *str = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
178 show_dot_files = IS_OPTION_TRUE( str[0] );
180 RtlInitUnicodeString( &nameW, ShowDirSymlinksW );
181 if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
183 WCHAR *str = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
184 show_dir_symlinks = IS_OPTION_TRUE( str[0] );
186 NtClose( hkey );
191 /***********************************************************************
192 * hash_short_file_name
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.
200 static ULONG hash_short_file_name( const UNICODE_STRING *name, LPWSTR buffer )
202 static const WCHAR invalid_chars[] = { INVALID_DOS_CHARS,'~','.',0 };
203 static const char hash_chars[32] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345";
205 LPCWSTR p, ext, end = name->Buffer + name->Length / sizeof(WCHAR);
206 LPWSTR dst;
207 unsigned short hash;
208 int i;
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)
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 */
219 else
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 */
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;
229 /* Copy first 4 chars, replacing invalid chars with '_' */
230 for (i = 4, p = name->Buffer, dst = buffer; i > 0; i--, p++)
232 if (p == end || p == ext) break;
233 *dst++ = strchrW( invalid_chars, *p ) ? '_' : toupperW(*p);
235 /* Pad to 5 chars with '~' */
236 while (i-- >= 0) *dst++ = '~';
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];
243 /* Copy the first 3 chars of the extension (if any) */
244 if (ext)
246 *dst++ = '.';
247 for (i = 3, ext++; (i > 0) && ext < end; i--, ext++)
248 *dst++ = strchrW( invalid_chars, *ext ) ? '_' : toupperW(*ext);
250 return dst - buffer;
254 /***********************************************************************
255 * match_filename
257 * Check a long file name against a mask.
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 *
270 static BOOLEAN match_filename( const UNICODE_STRING *name_str, const UNICODE_STRING *mask_str )
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;
280 TRACE("(%s, %s)\n", debugstr_us(name_str), debugstr_us(mask_str));
282 while (name < name_end && mask < mask_end)
284 switch(*mask)
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;
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));
307 if (!mismatch)
309 mask++;
310 name++;
311 if (mask == mask_end)
313 if (name == name_end) return TRUE;
314 if (lastjoker) mask = lastjoker;
317 else /* mismatch ! */
319 if (lastjoker) /* we had an '*', so we can try unlimitedly */
321 mask = lastjoker;
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;
328 else return FALSE; /* bad luck */
330 break;
333 while (mask < mask_end && ((*mask == '.') || (*mask == '*')))
334 mask++; /* Ignore trailing '.' or '*' in mask */
335 return (name == name_end && mask == mask_end);
339 /***********************************************************************
340 * append_entry
342 * helper for NtQueryDirectoryFile
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 )
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;
355 long_len = ntdll_umbstowcs( 0, long_name, strlen(long_name), long_nameW, MAX_DIR_ENTRY_LEN );
356 if (long_len == -1) return NULL;
358 str.Buffer = long_nameW;
359 str.Length = long_len * sizeof(WCHAR);
360 str.MaximumLength = sizeof(long_nameW);
362 if (short_name)
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);
368 else /* generate a short name if necessary */
370 BOOLEAN spaces;
372 short_len = 0;
373 if (!RtlIsNameLegalDOS8Dot3( &str, NULL, &spaces ) || spaces)
374 short_len = hash_short_file_name( &str, short_nameW );
377 TRACE( "long %s short %s mask %s\n",
378 debugstr_us(&str), debugstr_wn(short_nameW, short_len), debugstr_us(mask) );
380 if (mask && !match_filename( &str, mask ))
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;
389 total_len = (sizeof(*info) - sizeof(info->FileName) + long_len*sizeof(WCHAR) + 3) & ~3;
390 info = (FILE_BOTH_DIR_INFORMATION *)((char *)info_ptr + *pos);
392 if (*pos + total_len > max_length) total_len = max_length - *pos;
394 if (lstat( long_name, &st ) == -1) return NULL;
395 if (S_ISLNK( st.st_mode ))
397 if (stat( long_name, &st ) == -1) return NULL;
398 if (S_ISDIR( st.st_mode ) && !show_dir_symlinks) return NULL;
401 info->NextEntryOffset = total_len;
402 info->FileIndex = 0; /* NTFS always has 0 here, so let's not bother with it */
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 );
409 if (S_ISDIR(st.st_mode))
411 info->EndOfFile.QuadPart = info->AllocationSize.QuadPart = 0;
412 info->FileAttributes = FILE_ATTRIBUTE_DIRECTORY;
414 else
416 info->EndOfFile.QuadPart = st.st_size;
417 info->AllocationSize.QuadPart = (ULONGLONG)st.st_blocks * 512;
418 info->FileAttributes = FILE_ATTRIBUTE_ARCHIVE;
421 if (!(st.st_mode & S_IWUSR))
422 info->FileAttributes |= FILE_ATTRIBUTE_READONLY;
424 if (!show_dot_files && long_name[0] == '.' && long_name[1] && (long_name[1] != '.' || long_name[2]))
425 info->FileAttributes |= FILE_ATTRIBUTE_HIDDEN;
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) ));
434 *pos += total_len;
435 return info;
439 /******************************************************************************
440 * NtQueryDirectoryFile [NTDLL.@]
441 * ZwQueryDirectoryFile [NTDLL.@]
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 )
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);
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);
461 if (length < sizeof(*info)) return STATUS_INFO_LENGTH_MISMATCH;
463 if (event || apc_routine)
465 FIXME( "Unsupported yet option\n" );
466 return io->u.Status = STATUS_NOT_IMPLEMENTED;
468 if (info_class != FileBothDirectoryInformation)
470 FIXME( "Unsupported file info class %d\n", info_class );
471 return io->u.Status = STATUS_NOT_IMPLEMENTED;
474 if ((io->u.Status = wine_server_handle_to_fd( handle, GENERIC_READ,
475 &fd, NULL, NULL )) != STATUS_SUCCESS)
476 return io->u.Status;
478 io->Information = 0;
480 RtlEnterCriticalSection( &chdir_section );
482 if (show_dir_symlinks == -1) init_options();
484 if ((cwd = open(".", O_RDONLY)) != -1 && fchdir( fd ) != -1)
486 off_t old_pos = 0;
488 #ifdef VFAT_IOCTL_READDIR_BOTH
489 KERNEL_DIRENT de[2];
491 io->u.Status = STATUS_SUCCESS;
493 /* Check if the VFAT ioctl is supported on this directory */
495 if (restart_scan) lseek( fd, 0, SEEK_SET );
496 else old_pos = lseek( fd, 0, SEEK_CUR );
498 if (ioctl( fd, VFAT_IOCTL_READDIR_BOTH, (long)de ) != -1)
500 if (length < max_dir_info_size) /* we may have to return a partial entry here */
502 for (;;)
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)
513 last_info = info;
514 if ((char *)info->FileName + info->FileNameLength > (char *)buffer + length)
516 io->u.Status = STATUS_BUFFER_OVERFLOW;
517 lseek( fd, old_pos, SEEK_SET ); /* restore pos to previous entry */
519 break;
521 old_pos = lseek( fd, 0, SEEK_CUR );
522 if (ioctl( fd, VFAT_IOCTL_READDIR_BOTH, (long)de ) == -1) break;
525 else /* we'll only return full entries, no need to worry about overflow */
527 for (;;)
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)
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;
543 if (ioctl( fd, VFAT_IOCTL_READDIR_BOTH, (long)de ) == -1) break;
547 else if (errno != ENOENT)
548 #endif /* VFAT_IOCTL_READDIR_BOTH */
550 DIR *dir;
551 struct dirent *de;
553 if (!(dir = opendir( "." )))
555 io->u.Status = FILE_GetNtStatus();
556 goto done;
558 if (!restart_scan)
560 old_pos = lseek( fd, 0, SEEK_CUR );
561 seekdir_wrapper( dir, old_pos );
563 io->u.Status = STATUS_SUCCESS;
565 if (length < max_dir_info_size) /* we may have to return a partial entry here */
567 while ((de = readdir( dir )))
569 info = append_entry( buffer, &io->Information, length,
570 de->d_name, NULL, mask );
571 if (info)
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;
580 old_pos = telldir_wrapper( dir, old_pos, 1 );
583 else /* we'll only return full entries, no need to worry about overflow */
585 int count = 0;
586 while ((de = readdir( dir )))
588 count++;
589 info = append_entry( buffer, &io->Information, length,
590 de->d_name, NULL, mask );
591 if (info)
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;
599 old_pos = telldir_wrapper( dir, old_pos, count );
601 lseek( fd, old_pos, SEEK_SET ); /* store dir offset as filepos for fd */
602 closedir( dir );
605 if (last_info) last_info->NextEntryOffset = 0;
606 else io->u.Status = restart_scan ? STATUS_NO_SUCH_FILE : STATUS_NO_MORE_FILES;
608 done:
609 if (fchdir( cwd ) == -1) chdir( "/" );
611 else io->u.Status = FILE_GetNtStatus();
613 RtlLeaveCriticalSection( &chdir_section );
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;
622 /***********************************************************************
623 * find_file_in_dir
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.
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 )
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;
640 /* try a shortcut for this directory */
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)
649 unix_name[pos + ret] = 0;
650 if (!stat( unix_name, &st )) return STATUS_SUCCESS;
652 if (check_case) goto not_found; /* we want an exact match */
654 if (pos > 1) unix_name[pos - 1] = 0;
655 else unix_name[1] = 0; /* keep the initial slash */
657 /* check if it fits in 8.3 so that we don't look for short names if we won't need them */
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;
664 /* now look for it through the directory */
666 #ifdef VFAT_IOCTL_READDIR_BOTH
667 if (is_name_8_dot_3)
669 int fd = open( unix_name, O_RDONLY | O_DIRECTORY );
670 if (fd != -1)
672 KERNEL_DIRENT de[2];
674 if (ioctl( fd, VFAT_IOCTL_READDIR_BOTH, (long)de ) != -1)
676 unix_name[pos - 1] = '/';
677 for (;;)
679 if (!de[0].d_reclen) break;
681 if (de[1].d_name[0])
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))
687 strcpy( unix_name + pos, de[1].d_name );
688 close( fd );
689 return STATUS_SUCCESS;
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))
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;
701 if (ioctl( fd, VFAT_IOCTL_READDIR_BOTH, (long)de ) == -1)
703 close( fd );
704 goto not_found;
708 close( fd );
710 /* fall through to normal handling */
712 #endif /* VFAT_IOCTL_READDIR_BOTH */
714 if (!(dir = opendir( unix_name )))
716 if (errno == ENOENT) return STATUS_OBJECT_PATH_NOT_FOUND;
717 else return FILE_GetNtStatus();
719 unix_name[pos - 1] = '/';
720 str.Buffer = buffer;
721 str.MaximumLength = sizeof(buffer);
722 while ((de = readdir( dir )))
724 ret = ntdll_umbstowcs( 0, de->d_name, strlen(de->d_name), buffer, MAX_DIR_ENTRY_LEN );
725 if (ret == length && !memicmpW( buffer, name, length ))
727 strcpy( unix_name + pos, de->d_name );
728 closedir( dir );
729 return STATUS_SUCCESS;
732 if (!is_name_8_dot_3) continue;
734 str.Length = ret * sizeof(WCHAR);
735 if (!RtlIsNameLegalDOS8Dot3( &str, NULL, &spaces ) || spaces)
737 WCHAR short_nameW[12];
738 ret = hash_short_file_name( &str, short_nameW );
739 if (ret == length && !memicmpW( short_nameW, name, length ))
741 strcpy( unix_name + pos, de->d_name );
742 closedir( dir );
743 return STATUS_SUCCESS;
747 closedir( dir );
748 goto not_found; /* avoid warning */
750 not_found:
751 if (is_last && !check_last) /* return the name anyway */
753 int used_default;
754 ret = ntdll_wcstoumbs( 0, name, length, unix_name + pos,
755 MAX_DIR_ENTRY_LEN, NULL, &used_default );
756 if (ret > 0 && !used_default)
758 unix_name[pos + ret] = 0;
759 return STATUS_SUCCESS;
762 unix_name[pos - 1] = 0;
763 return is_last ? STATUS_OBJECT_NAME_NOT_FOUND : STATUS_OBJECT_PATH_NOT_FOUND;
767 /* return the length of the DOS namespace prefix if any */
768 static inline int get_dos_prefix_len( const UNICODE_STRING *name )
770 static const WCHAR nt_prefixW[] = {'\\','?','?','\\'};
771 static const WCHAR dosdev_prefixW[] = {'\\','D','o','s','D','e','v','i','c','e','s','\\'};
773 if (name->Length > sizeof(nt_prefixW) &&
774 !memcmp( name->Buffer, nt_prefixW, sizeof(nt_prefixW) ))
775 return sizeof(nt_prefixW) / sizeof(WCHAR);
777 if (name->Length > sizeof(dosdev_prefixW) &&
778 !memicmpW( name->Buffer, dosdev_prefixW, sizeof(dosdev_prefixW)/sizeof(WCHAR) ))
779 return sizeof(dosdev_prefixW) / sizeof(WCHAR);
781 return 0;
785 /******************************************************************************
786 * DIR_nt_to_unix
788 * Convert a file name from NT namespace to Unix namespace.
790 NTSTATUS DIR_nt_to_unix( const UNICODE_STRING *nameW, ANSI_STRING *unix_name_ret,
791 int check_last, int check_case )
793 static const WCHAR uncW[] = {'U','N','C','\\'};
794 static const WCHAR invalid_charsW[] = { INVALID_NT_CHARS, 0 };
796 NTSTATUS status = STATUS_OBJECT_NAME_NOT_FOUND;
797 const char *config_dir = wine_get_config_dir();
798 const WCHAR *name, *p;
799 struct stat st;
800 char *unix_name;
801 int pos, ret, name_len, unix_len, used_default;
803 name = nameW->Buffer;
804 name_len = nameW->Length / sizeof(WCHAR);
806 if (!name_len || !IS_SEPARATOR(name[0])) return STATUS_OBJECT_PATH_SYNTAX_BAD;
808 if ((pos = get_dos_prefix_len( nameW )))
810 name += pos;
811 name_len -= pos;
813 /* check for UNC prefix */
814 if (name_len > 4 && !memicmpW( name, uncW, 4 ))
816 FIXME( "UNC name %s not supported\n", debugstr_us(nameW) );
817 return STATUS_OBJECT_NAME_NOT_FOUND;
820 /* make sure we have a drive letter */
821 if (name_len < 3 || !isalphaW(name[0]) || name[1] != ':' || !IS_SEPARATOR(name[2]))
822 return STATUS_OBJECT_NAME_NOT_FOUND;
823 name += 2; /* skip drive letter */
824 name_len -= 2;
826 /* check for invalid characters */
827 for (p = name; p < name + name_len; p++)
828 if (*p < 32 || strchrW( invalid_charsW, *p )) return STATUS_OBJECT_NAME_INVALID;
830 unix_len = ntdll_wcstoumbs( 0, name, name_len, NULL, 0, NULL, NULL );
831 unix_len += MAX_DIR_ENTRY_LEN + 3;
832 unix_len += strlen(config_dir) + sizeof("/dosdevices/a:");
833 if (!(unix_name = RtlAllocateHeap( GetProcessHeap(), 0, unix_len )))
834 return STATUS_NO_MEMORY;
835 strcpy( unix_name, config_dir );
836 strcat( unix_name, "/dosdevices/a:" );
837 pos = strlen(unix_name);
838 unix_name[pos - 2] = tolowerW( name[-2] );
840 else /* no DOS prefix, assume NT native name, map directly to Unix */
842 if (!name_len || !IS_SEPARATOR(name[0])) return STATUS_OBJECT_NAME_INVALID;
843 unix_len = ntdll_wcstoumbs( 0, name, name_len, NULL, 0, NULL, NULL );
844 unix_len += MAX_DIR_ENTRY_LEN + 3;
845 if (!(unix_name = RtlAllocateHeap( GetProcessHeap(), 0, unix_len )))
846 return STATUS_NO_MEMORY;
847 pos = 0;
850 /* try a shortcut first */
852 ret = ntdll_wcstoumbs( 0, name, name_len, unix_name + pos, unix_len - pos - 1,
853 NULL, &used_default );
854 if (ret > 0 && !used_default) /* if we used the default char the name didn't convert properly */
856 char *p;
857 unix_name[pos + ret] = 0;
858 for (p = unix_name + pos ; *p; p++) if (*p == '\\') *p = '/';
859 if (!stat( unix_name, &st )) goto done;
862 while (name_len && IS_SEPARATOR(*name))
864 name++;
865 name_len--;
867 if (!name_len) /* empty name -> drive root doesn't exist */
869 RtlFreeHeap( GetProcessHeap(), 0, unix_name );
870 return STATUS_OBJECT_PATH_NOT_FOUND;
872 if (check_case && check_last)
874 RtlFreeHeap( GetProcessHeap(), 0, unix_name );
875 return STATUS_OBJECT_NAME_NOT_FOUND;
878 /* now do it component by component */
880 while (name_len)
882 const WCHAR *end, *next;
884 end = name;
885 while (end < name + name_len && !IS_SEPARATOR(*end)) end++;
886 next = end;
887 while (next < name + name_len && IS_SEPARATOR(*next)) next++;
889 /* grow the buffer if needed */
891 if (unix_len - pos < MAX_DIR_ENTRY_LEN + 2)
893 char *new_name;
894 unix_len += 2 * MAX_DIR_ENTRY_LEN;
895 if (!(new_name = RtlReAllocateHeap( GetProcessHeap(), 0, unix_name, unix_len )))
897 RtlFreeHeap( GetProcessHeap(), 0, unix_name );
898 return STATUS_NO_MEMORY;
900 unix_name = new_name;
903 status = find_file_in_dir( unix_name, pos, name, end - name,
904 (next - name == name_len), check_last, check_case );
905 if (status != STATUS_SUCCESS)
907 /* couldn't find it at all, fail */
908 WARN( "%s not found in %s\n", debugstr_w(name), unix_name );
909 RtlFreeHeap( GetProcessHeap(), 0, unix_name );
910 return status;
913 pos += strlen( unix_name + pos );
914 name_len -= next - name;
915 name = next;
918 WARN( "%s -> %s required a case-insensitive search\n",
919 debugstr_us(nameW), debugstr_a(unix_name) );
921 done:
922 TRACE( "%s -> %s\n", debugstr_us(nameW), debugstr_a(unix_name) );
923 unix_name_ret->Buffer = unix_name;
924 unix_name_ret->Length = strlen(unix_name);
925 unix_name_ret->MaximumLength = unix_len;
926 return STATUS_SUCCESS;
930 /***********************************************************************
931 * wine_get_unix_file_name (NTDLL.@) Not a Windows API
933 * Return the full Unix file name for a given path.
934 * Returned buffer must be freed by caller.
936 char *wine_get_unix_file_name( LPCWSTR dosW )
938 UNICODE_STRING nt_name;
939 ANSI_STRING unix_name;
940 NTSTATUS status;
942 if (!RtlDosPathNameToNtPathName_U( dosW, &nt_name, NULL, NULL )) return NULL;
943 status = DIR_nt_to_unix( &nt_name, &unix_name, FALSE, FALSE );
944 RtlFreeUnicodeString( &nt_name );
945 if (status) return NULL;
946 return unix_name.Buffer;
950 /******************************************************************
951 * RtlDoesFileExists_U (NTDLL.@)
953 BOOLEAN WINAPI RtlDoesFileExists_U(LPCWSTR file_name)
955 UNICODE_STRING nt_name;
956 ANSI_STRING unix_name;
957 BOOLEAN ret;
959 if (!RtlDosPathNameToNtPathName_U( file_name, &nt_name, NULL, NULL )) return FALSE;
960 ret = (DIR_nt_to_unix( &nt_name, &unix_name, TRUE, FALSE ) == STATUS_SUCCESS);
961 if (ret) RtlFreeAnsiString( &unix_name );
962 RtlFreeUnicodeString( &nt_name );
963 return ret;