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