Make IS_END_OF_NAME an inline function.
[wine] / files / dos_fs.c
1 /*
2  * DOS file system functions
3  *
4  * Copyright 1993 Erik Bos
5  * Copyright 1996 Alexandre Julliard
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  */
21
22 #include "config.h"
23 #include "wine/port.h"
24
25 #include <sys/types.h>
26 #include <ctype.h>
27 #include <dirent.h>
28 #include <errno.h>
29 #ifdef HAVE_SYS_ERRNO_H
30 #include <sys/errno.h>
31 #endif
32 #include <fcntl.h>
33 #include <stdarg.h>
34 #include <string.h>
35 #include <stdlib.h>
36 #include <sys/stat.h>
37 #ifdef HAVE_SYS_IOCTL_H
38 #include <sys/ioctl.h>
39 #endif
40 #ifdef HAVE_LINUX_IOCTL_H
41 #include <linux/ioctl.h>
42 #endif
43 #include <time.h>
44 #ifdef HAVE_UNISTD_H
45 # include <unistd.h>
46 #endif
47
48 #define NONAMELESSUNION
49 #define NONAMELESSSTRUCT
50 #include "ntstatus.h"
51 #include "windef.h"
52 #include "winbase.h"
53 #include "winerror.h"
54 #include "wingdi.h"
55
56 #include "wine/unicode.h"
57 #include "wine/winbase16.h"
58 #include "file.h"
59 #include "winreg.h"
60 #include "winternl.h"
61 #include "wine/server.h"
62 #include "wine/exception.h"
63 #include "excpt.h"
64
65 #include "smb.h"
66
67 #include "wine/debug.h"
68
69 WINE_DEFAULT_DEBUG_CHANNEL(dosfs);
70 WINE_DECLARE_DEBUG_CHANNEL(file);
71
72 /* Define the VFAT ioctl to get both short and long file names */
73 /* FIXME: is it possible to get this to work on other systems? */
74 #ifdef linux
75 /* We want the real kernel dirent structure, not the libc one */
76 typedef struct
77 {
78     long d_ino;
79     long d_off;
80     unsigned short d_reclen;
81     char d_name[256];
82 } KERNEL_DIRENT;
83
84 #define VFAT_IOCTL_READDIR_BOTH  _IOR('r', 1, KERNEL_DIRENT [2] )
85
86 /* To avoid blocking on non-directories in DOSFS_OpenDir_VFAT*/
87 #ifndef O_DIRECTORY
88 # define O_DIRECTORY    0200000 /* must be directory */
89 #endif
90
91 #else   /* linux */
92 #undef VFAT_IOCTL_READDIR_BOTH  /* just in case... */
93 #endif  /* linux */
94
95 #define IS_OPTION_TRUE(ch) ((ch) == 'y' || (ch) == 'Y' || (ch) == 't' || (ch) == 'T' || (ch) == '1')
96
97 /* Chars we don't want to see in DOS file names */
98 #define INVALID_DOS_CHARS  "*?<>|\"+=,;[] \345"
99
100 /* DOS device descriptor */
101 typedef struct
102 {
103     const WCHAR name[5];
104 } DOS_DEVICE;
105
106 static const DOS_DEVICE DOSFS_Devices[] =
107 /* name, device flags (see Int 21/AX=0x4400) */
108 {
109     { {'C','O','N',0} },
110     { {'P','R','N',0} },
111     { {'N','U','L',0} },
112     { {'A','U','X',0} },
113     { {'L','P','T','1',0} },
114     { {'L','P','T','2',0} },
115     { {'L','P','T','3',0} },
116     { {'L','P','T','4',0} },
117     { {'C','O','M','1',0} },
118     { {'C','O','M','2',0} },
119     { {'C','O','M','3',0} },
120     { {'C','O','M','4',0} }
121 };
122
123 static const WCHAR devW[] = {'\\','D','e','v','i','c','e','\\',0};
124 static const WCHAR dosW[] = {'\\','D','o','s','D','e','v','i','c','e','s','\\',0};
125
126 static const WCHAR auxW[] = {'A','U','X',0};
127 static const WCHAR comW[] = {'C','O','M',0};
128 static const WCHAR lptW[] = {'L','P','T',0};
129 static const WCHAR nulW[] = {'N','U','L',0};
130
131 static const WCHAR nullW[] = {'N','u','l','l',0};
132 static const WCHAR parW[] = {'P','a','r','a','l','l','e','l',0};
133 static const WCHAR serW[] = {'S','e','r','i','a','l',0};
134 static const WCHAR oneW[] = {'1',0};
135
136 /* at some point we may want to allow Winelib apps to set this */
137 static const BOOL is_case_sensitive = FALSE;
138
139 /*
140  * Directory info for DOSFS_ReadDir
141  * contains the names of *all* the files in the directory
142  */
143 typedef struct
144 {
145     int used;
146     int size;
147     WCHAR names[1];
148 } DOS_DIR;
149
150 /* Info structure for FindFirstFile handle */
151 typedef struct
152 {
153     char *path; /* unix path */
154     LPWSTR long_mask;
155     int   drive;
156     int   cur_pos;
157     CRITICAL_SECTION cs;
158     union
159     {
160         DOS_DIR *dos_dir;
161         SMB_DIR *smb_dir;
162     } u;
163 } FIND_FIRST_INFO;
164
165
166 static WINE_EXCEPTION_FILTER(page_fault)
167 {
168     if (GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION)
169         return EXCEPTION_EXECUTE_HANDLER;
170     return EXCEPTION_CONTINUE_SEARCH;
171 }
172
173
174 /* return non-zero if c is the end of a directory name */
175 static inline int is_end_of_name(WCHAR c)
176 {
177     return !c || (c == '/') || (c == '\\');
178 }
179
180 /***********************************************************************
181  *           DOSFS_ValidDOSName
182  *
183  * Return 1 if Unix file 'name' is also a valid MS-DOS name
184  * (i.e. contains only valid DOS chars, lower-case only, fits in 8.3 format).
185  * File name can be terminated by '\0', '\\' or '/'.
186  */
187 static int DOSFS_ValidDOSName( LPCWSTR name )
188 {
189     static const char invalid_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" INVALID_DOS_CHARS;
190     const WCHAR *p = name;
191     const char *invalid = !is_case_sensitive ? (invalid_chars + 26) : invalid_chars;
192     int len = 0;
193
194     if (*p == '.')
195     {
196         /* Check for "." and ".." */
197         p++;
198         if (*p == '.') p++;
199         /* All other names beginning with '.' are invalid */
200         return (is_end_of_name(*p));
201     }
202     while (!is_end_of_name(*p))
203     {
204         if (*p < 256 && strchr( invalid, (char)*p )) return 0;  /* Invalid char */
205         if (*p == '.') break;  /* Start of the extension */
206         if (++len > 8) return 0;  /* Name too long */
207         p++;
208     }
209     if (*p != '.') return 1;  /* End of name */
210     p++;
211     if (is_end_of_name(*p)) return 0;  /* Empty extension not allowed */
212     len = 0;
213     while (!is_end_of_name(*p))
214     {
215         if (*p < 256 && strchr( invalid, (char)*p )) return 0;  /* Invalid char */
216         if (*p == '.') return 0;  /* Second extension not allowed */
217         if (++len > 3) return 0;  /* Extension too long */
218         p++;
219     }
220     return 1;
221 }
222
223
224 /***********************************************************************
225  *           DOSFS_ToDosFCBFormat
226  *
227  * Convert a file name to DOS FCB format (8+3 chars, padded with blanks),
228  * expanding wild cards and converting to upper-case in the process.
229  * File name can be terminated by '\0', '\\' or '/'.
230  * Return FALSE if the name is not a valid DOS name.
231  * 'buffer' must be at least 12 characters long.
232  */
233 static BOOL DOSFS_ToDosFCBFormat( LPCWSTR name, LPWSTR buffer )
234 {
235     static const char invalid_chars[] = INVALID_DOS_CHARS;
236     LPCWSTR p = name;
237     int i;
238
239     /* Check for "." and ".." */
240     if (*p == '.')
241     {
242         p++;
243         buffer[0] = '.';
244         for(i = 1; i < 11; i++) buffer[i] = ' ';
245         buffer[11] = 0;
246         if (*p == '.')
247         {
248             buffer[1] = '.';
249             p++;
250         }
251         return (!*p || (*p == '/') || (*p == '\\'));
252     }
253
254     for (i = 0; i < 8; i++)
255     {
256         switch(*p)
257         {
258         case '\0':
259         case '\\':
260         case '/':
261         case '.':
262             buffer[i] = ' ';
263             break;
264         case '?':
265             p++;
266             /* fall through */
267         case '*':
268             buffer[i] = '?';
269             break;
270         default:
271             if (*p < 256 && strchr( invalid_chars, (char)*p )) return FALSE;
272             buffer[i] = toupperW(*p);
273             p++;
274             break;
275         }
276     }
277
278     if (*p == '*')
279     {
280         /* Skip all chars after wildcard up to first dot */
281         while (*p && (*p != '/') && (*p != '\\') && (*p != '.')) p++;
282     }
283     else
284     {
285         /* Check if name too long */
286         if (*p && (*p != '/') && (*p != '\\') && (*p != '.')) return FALSE;
287     }
288     if (*p == '.') p++;  /* Skip dot */
289
290     for (i = 8; i < 11; i++)
291     {
292         switch(*p)
293         {
294         case '\0':
295         case '\\':
296         case '/':
297             buffer[i] = ' ';
298             break;
299         case '.':
300             return FALSE;  /* Second extension not allowed */
301         case '?':
302             p++;
303             /* fall through */
304         case '*':
305             buffer[i] = '?';
306             break;
307         default:
308             if (*p < 256 && strchr( invalid_chars, (char)*p )) return FALSE;
309             buffer[i] = toupperW(*p);
310             p++;
311             break;
312         }
313     }
314     buffer[11] = '\0';
315
316     /* at most 3 character of the extension are processed
317      * is something behind this ?
318      */
319     while (*p == '*' || *p == ' ') p++; /* skip wildcards and spaces */
320     return is_end_of_name(*p);
321 }
322
323
324 /***********************************************************************
325  *           DOSFS_ToDosDTAFormat
326  *
327  * Convert a file name from FCB to DTA format (name.ext, null-terminated)
328  * converting to upper-case in the process.
329  * File name can be terminated by '\0', '\\' or '/'.
330  * 'buffer' must be at least 13 characters long.
331  */
332 static void DOSFS_ToDosDTAFormat( LPCWSTR name, LPWSTR buffer )
333 {
334     LPWSTR p;
335
336     memcpy( buffer, name, 8 * sizeof(WCHAR) );
337     p = buffer + 8;
338     while ((p > buffer) && (p[-1] == ' ')) p--;
339     *p++ = '.';
340     memcpy( p, name + 8, 3 * sizeof(WCHAR) );
341     p += 3;
342     while (p[-1] == ' ') p--;
343     if (p[-1] == '.') p--;
344     *p = '\0';
345 }
346
347
348 /***********************************************************************
349  *           DOSFS_MatchLong
350  *
351  * Check a long file name against a mask.
352  *
353  * Tests (done in W95 DOS shell - case insensitive):
354  * *.txt                        test1.test.txt                          *
355  * *st1*                        test1.txt                               *
356  * *.t??????.t*                 test1.ta.tornado.txt                    *
357  * *tornado*                    test1.ta.tornado.txt                    *
358  * t*t                          test1.ta.tornado.txt                    *
359  * ?est*                        test1.txt                               *
360  * ?est???                      test1.txt                               -
361  * *test1.txt*                  test1.txt                               *
362  * h?l?o*t.dat                  hellothisisatest.dat                    *
363  */
364 static int DOSFS_MatchLong( LPCWSTR mask, LPCWSTR name )
365 {
366     LPCWSTR lastjoker = NULL;
367     LPCWSTR next_to_retry = NULL;
368     static const WCHAR asterisk_dot_asterisk[] = {'*','.','*',0};
369
370     TRACE("(%s, %s)\n", debugstr_w(mask), debugstr_w(name));
371
372     if (!strcmpW( mask, asterisk_dot_asterisk )) return 1;
373     while (*name && *mask)
374     {
375         if (*mask == '*')
376         {
377             mask++;
378             while (*mask == '*') mask++;  /* Skip consecutive '*' */
379             lastjoker = mask;
380             if (!*mask) return 1; /* end of mask is all '*', so match */
381
382             /* skip to the next match after the joker(s) */
383             if (is_case_sensitive) while (*name && (*name != *mask)) name++;
384             else while (*name && (toupperW(*name) != toupperW(*mask))) name++;
385
386             if (!*name) break;
387             next_to_retry = name;
388         }
389         else if (*mask != '?')
390         {
391             int mismatch = 0;
392             if (is_case_sensitive)
393             {
394                 if (*mask != *name) mismatch = 1;
395             }
396             else
397             {
398                 if (toupperW(*mask) != toupperW(*name)) mismatch = 1;
399             }
400             if (!mismatch)
401             {
402                 mask++;
403                 name++;
404                 if (*mask == '\0')
405                 {
406                     if (*name == '\0')
407                         return 1;
408                     if (lastjoker)
409                         mask = lastjoker;
410                 }
411             }
412             else /* mismatch ! */
413             {
414                 if (lastjoker) /* we had an '*', so we can try unlimitedly */
415                 {
416                     mask = lastjoker;
417
418                     /* this scan sequence was a mismatch, so restart
419                      * 1 char after the first char we checked last time */
420                     next_to_retry++;
421                     name = next_to_retry;
422                 }
423                 else
424                     return 0; /* bad luck */
425             }
426         }
427         else /* '?' */
428         {
429             mask++;
430             name++;
431         }
432     }
433     while ((*mask == '.') || (*mask == '*'))
434         mask++;  /* Ignore trailing '.' or '*' in mask */
435     return (!*name && !*mask);
436 }
437
438
439 /***********************************************************************
440  *           DOSFS_AddDirEntry
441  *
442  *  Used to construct an array of filenames in DOSFS_OpenDir
443  */
444 static BOOL DOSFS_AddDirEntry(DOS_DIR **dir, LPCWSTR name, LPCWSTR dosname)
445 {
446     int extra1 = strlenW(name) + 1;
447     int extra2 = strlenW(dosname) + 1;
448
449     /* if we need more, at minimum double the size */
450     if( (extra1 + extra2 + (*dir)->used) > (*dir)->size)
451     {
452         int more = (*dir)->size;
453         DOS_DIR *t;
454
455         if(more<(extra1+extra2))
456             more = extra1+extra2;
457
458         t = HeapReAlloc(GetProcessHeap(), 0, *dir, sizeof(**dir) + 
459                         ((*dir)->size + more)*sizeof(WCHAR) );
460         if(!t)
461         {
462             SetLastError( ERROR_NOT_ENOUGH_MEMORY );
463             ERR("Out of memory caching directory structure %d %d %d\n",
464                  (*dir)->size, more, (*dir)->used);
465             return FALSE;
466         }
467         (*dir) = t;
468         (*dir)->size += more;
469     }
470
471     /* at this point, the dir structure is big enough to hold these names */
472     strcpyW(&(*dir)->names[(*dir)->used], name);
473     (*dir)->used += extra1;
474     strcpyW(&(*dir)->names[(*dir)->used], dosname);
475     (*dir)->used += extra2;
476
477     return TRUE;
478 }
479
480
481 /***********************************************************************
482  *           DOSFS_OpenDir_VFAT
483  */
484 static BOOL DOSFS_OpenDir_VFAT(DOS_DIR **dir, const char *unix_path)
485 {
486 #ifdef VFAT_IOCTL_READDIR_BOTH
487     KERNEL_DIRENT de[2];
488     int fd = open( unix_path, O_RDONLY|O_DIRECTORY );
489     BOOL r = TRUE;
490
491     /* Check if the VFAT ioctl is supported on this directory */
492
493     if ( fd<0 )
494         return FALSE;
495
496     while (1)
497     {
498         WCHAR long_name[MAX_PATH];
499         WCHAR short_name[12];
500
501         r = (ioctl( fd, VFAT_IOCTL_READDIR_BOTH, (long)de ) != -1);
502         if(!r)
503             break;
504         if (!de[0].d_reclen)
505             break;
506         MultiByteToWideChar(CP_UNIXCP, 0, de[0].d_name, -1, long_name, MAX_PATH);
507         if (!DOSFS_ToDosFCBFormat( long_name, short_name ))
508             short_name[0] = '\0';
509         if (de[1].d_name[0])
510             MultiByteToWideChar(CP_UNIXCP, 0, de[1].d_name, -1, long_name, MAX_PATH);
511         else
512             MultiByteToWideChar(CP_UNIXCP, 0, de[0].d_name, -1, long_name, MAX_PATH);
513         r = DOSFS_AddDirEntry(dir, long_name, short_name );
514         if(!r)
515             break;
516     }
517     if(r)
518     {
519         static const WCHAR empty_strW[] = { 0 };
520         DOSFS_AddDirEntry(dir, empty_strW, empty_strW);
521     }
522     close(fd);
523     return r;
524 #else
525     return FALSE;
526 #endif  /* VFAT_IOCTL_READDIR_BOTH */
527 }
528
529
530 /***********************************************************************
531  *           DOSFS_OpenDir_Normal
532  *
533  * Now use the standard opendir/readdir interface
534  */
535 static BOOL DOSFS_OpenDir_Normal( DOS_DIR **dir, const char *unix_path )
536 {
537     DIR *unixdir = opendir( unix_path );
538     BOOL r = TRUE;
539     static const WCHAR empty_strW[] = { 0 };
540
541     if(!unixdir)
542         return FALSE;
543     while(1)
544     {
545         WCHAR long_name[MAX_PATH];
546         struct dirent *de = readdir(unixdir);
547
548         if(!de)
549             break;
550         MultiByteToWideChar(CP_UNIXCP, 0, de->d_name, -1, long_name, MAX_PATH);
551         r = DOSFS_AddDirEntry(dir, long_name, empty_strW);
552         if(!r)
553             break;
554     }
555     if(r)
556         DOSFS_AddDirEntry(dir, empty_strW, empty_strW);
557     closedir(unixdir);
558     return r;
559 }
560
561 /***********************************************************************
562  *           DOSFS_OpenDir
563  */
564 static DOS_DIR *DOSFS_OpenDir( const char *unix_path )
565 {
566     const int init_size = 0x100;
567     DOS_DIR *dir = HeapAlloc( GetProcessHeap(), 0, sizeof(*dir) + init_size*sizeof (WCHAR));
568     BOOL r;
569
570     TRACE("%s\n",debugstr_a(unix_path));
571
572     if (!dir)
573     {
574         SetLastError( ERROR_NOT_ENOUGH_MEMORY );
575         return NULL;
576     }
577     dir->used = 0;
578     dir->size = init_size;
579
580     /* Treat empty path as root directory. This simplifies path split into
581        directory and mask in several other places */
582     if (!*unix_path) unix_path = "/";
583
584     r = DOSFS_OpenDir_VFAT( &dir, unix_path);
585
586     if(!r)
587         r = DOSFS_OpenDir_Normal( &dir, unix_path);
588
589     if(!r)
590     {
591         HeapFree(GetProcessHeap(), 0, dir);
592         return NULL;
593     }
594     dir->used = 0;
595
596     return dir;
597 }
598
599
600 /***********************************************************************
601  *           DOSFS_CloseDir
602  */
603 static void DOSFS_CloseDir( DOS_DIR *dir )
604 {
605     HeapFree( GetProcessHeap(), 0, dir );
606 }
607
608
609 /***********************************************************************
610  *           DOSFS_ReadDir
611  */
612 static BOOL DOSFS_ReadDir( DOS_DIR *dir, LPCWSTR *long_name,
613                              LPCWSTR *short_name )
614 {
615     LPCWSTR sn, ln;
616
617     if (!dir)
618        return FALSE;
619
620     /* the long pathname is first */
621     ln = &dir->names[dir->used];
622     if(ln[0])
623         *long_name  = ln;
624     else
625         return FALSE;
626     dir->used += (strlenW(ln) + 1);
627
628     /* followed by the short path name */
629     sn = &dir->names[dir->used];
630     if(sn[0])
631         *short_name = sn;
632     else
633         *short_name = NULL;
634     dir->used += (strlenW(sn) + 1);
635
636     return TRUE;
637 }
638
639
640 /***********************************************************************
641  *           DOSFS_Hash
642  *
643  * Transform a Unix file name into a hashed DOS name. If the name is a valid
644  * DOS name, it is converted to upper-case; otherwise it is replaced by a
645  * hashed version that fits in 8.3 format.
646  * File name can be terminated by '\0', '\\' or '/'.
647  * 'buffer' must be at least 13 characters long.
648  */
649 static void DOSFS_Hash( LPCWSTR name, LPWSTR buffer, BOOL dir_format )
650 {
651     static const char invalid_chars[] = INVALID_DOS_CHARS "~.";
652     static const char hash_chars[32] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345";
653
654     LPCWSTR p, ext;
655     LPWSTR dst;
656     unsigned short hash;
657     int i;
658
659     if (dir_format)
660     {
661         for(i = 0; i < 11; i++) buffer[i] = ' ';
662         buffer[11] = 0;
663     }
664
665     if (DOSFS_ValidDOSName( name ))
666     {
667         /* Check for '.' and '..' */
668         if (*name == '.')
669         {
670             buffer[0] = '.';
671             if (!dir_format) buffer[1] = buffer[2] = '\0';
672             if (name[1] == '.') buffer[1] = '.';
673             return;
674         }
675
676         /* Simply copy the name, converting to uppercase */
677
678         for (dst = buffer; !is_end_of_name(*name) && (*name != '.'); name++)
679             *dst++ = toupperW(*name);
680         if (*name == '.')
681         {
682             if (dir_format) dst = buffer + 8;
683             else *dst++ = '.';
684             for (name++; !is_end_of_name(*name); name++)
685                 *dst++ = toupperW(*name);
686         }
687         if (!dir_format) *dst = '\0';
688         return;
689     }
690
691     /* Compute the hash code of the file name */
692     /* If you know something about hash functions, feel free to */
693     /* insert a better algorithm here... */
694     if (!is_case_sensitive)
695     {
696         for (p = name, hash = 0xbeef; !is_end_of_name(p[1]); p++)
697             hash = (hash<<3) ^ (hash>>5) ^ tolowerW(*p) ^ (tolowerW(p[1]) << 8);
698         hash = (hash<<3) ^ (hash>>5) ^ tolowerW(*p); /* Last character */
699     }
700     else
701     {
702         for (p = name, hash = 0xbeef; !is_end_of_name(p[1]); p++)
703             hash = (hash << 3) ^ (hash >> 5) ^ *p ^ (p[1] << 8);
704         hash = (hash << 3) ^ (hash >> 5) ^ *p;  /* Last character */
705     }
706
707     /* Find last dot for start of the extension */
708     for (p = name+1, ext = NULL; !is_end_of_name(*p); p++)
709         if (*p == '.') ext = p;
710     if (ext && is_end_of_name(ext[1]))
711         ext = NULL;  /* Empty extension ignored */
712
713     /* Copy first 4 chars, replacing invalid chars with '_' */
714     for (i = 4, p = name, dst = buffer; i > 0; i--, p++)
715     {
716         if (is_end_of_name(*p) || (p == ext)) break;
717         *dst++ = (*p < 256 && strchr( invalid_chars, (char)*p )) ? '_' : toupperW(*p);
718     }
719     /* Pad to 5 chars with '~' */
720     while (i-- >= 0) *dst++ = '~';
721
722     /* Insert hash code converted to 3 ASCII chars */
723     *dst++ = hash_chars[(hash >> 10) & 0x1f];
724     *dst++ = hash_chars[(hash >> 5) & 0x1f];
725     *dst++ = hash_chars[hash & 0x1f];
726
727     /* Copy the first 3 chars of the extension (if any) */
728     if (ext)
729     {
730         if (!dir_format) *dst++ = '.';
731         for (i = 3, ext++; (i > 0) && !is_end_of_name(*ext); i--, ext++)
732             *dst++ = (*ext < 256 && strchr( invalid_chars, (char)*ext )) ? '_' : toupperW(*ext);
733     }
734     if (!dir_format) *dst = '\0';
735 }
736
737
738 /***********************************************************************
739  *           DOSFS_FindUnixName
740  *
741  * Find the Unix file name in a given directory that corresponds to
742  * a file name (either in Unix or DOS format).
743  * File name can be terminated by '\0', '\\' or '/'.
744  * Return TRUE if OK, FALSE if no file name matches.
745  *
746  * 'long_buf' must be at least 'long_len' characters long. If the long name
747  * turns out to be larger than that, the function returns FALSE.
748  * 'short_buf' must be at least 13 characters long.
749  */
750 BOOL DOSFS_FindUnixName( const DOS_FULL_NAME *path, LPCWSTR name, char *long_buf,
751                          INT long_len, LPWSTR short_buf )
752 {
753     DOS_DIR *dir;
754     LPCWSTR long_name, short_name;
755     WCHAR dos_name[12], tmp_buf[13];
756     BOOL ret;
757
758     LPCWSTR p = strchrW( name, '/' );
759     int len = p ? (int)(p - name) : strlenW(name);
760     if ((p = strchrW( name, '\\' ))) len = min( (int)(p - name), len );
761     /* Ignore trailing dots and spaces */
762     while (len > 1 && (name[len-1] == '.' || name[len-1] == ' ')) len--;
763     if (long_len < len + 1) return FALSE;
764
765     TRACE("%s,%s\n", path->long_name, debugstr_w(name) );
766
767     if (!DOSFS_ToDosFCBFormat( name, dos_name )) dos_name[0] = '\0';
768
769     if (!(dir = DOSFS_OpenDir( path->long_name )))
770     {
771         WARN("(%s,%s): can't open dir: %s\n",
772              path->long_name, debugstr_w(name), strerror(errno) );
773         return FALSE;
774     }
775
776     while ((ret = DOSFS_ReadDir( dir, &long_name, &short_name )))
777     {
778         /* Check against Unix name */
779         if (len == strlenW(long_name))
780         {
781             if (is_case_sensitive)
782             {
783                 if (!strncmpW( long_name, name, len )) break;
784             }
785             else
786             {
787                 if (!strncmpiW( long_name, name, len )) break;
788             }
789         }
790         if (dos_name[0])
791         {
792             /* Check against hashed DOS name */
793             if (!short_name)
794             {
795                 DOSFS_Hash( long_name, tmp_buf, TRUE );
796                 short_name = tmp_buf;
797             }
798             if (!strcmpW( dos_name, short_name )) break;
799         }
800     }
801     if (ret)
802     {
803         if (long_buf) WideCharToMultiByte(CP_UNIXCP, 0, long_name, -1, long_buf, long_len, NULL, NULL);
804         if (short_buf)
805         {
806             if (short_name)
807                 DOSFS_ToDosDTAFormat( short_name, short_buf );
808             else
809                 DOSFS_Hash( long_name, short_buf, FALSE );
810         }
811         TRACE("(%s,%s) -> %s (%s)\n", path->long_name, debugstr_w(name),
812               debugstr_w(long_name), short_buf ? debugstr_w(short_buf) : "***");
813     }
814     else
815         WARN("%s not found in '%s'\n", debugstr_w(name), path->long_name);
816     DOSFS_CloseDir( dir );
817     return ret;
818 }
819
820
821 /**************************************************************************
822  *         DOSFS_CreateCommPort
823  */
824 static HANDLE DOSFS_CreateCommPort(LPCWSTR name, DWORD access, DWORD attributes, LPSECURITY_ATTRIBUTES sa)
825 {
826     HANDLE ret;
827     HKEY hkey;
828     DWORD dummy;
829     OBJECT_ATTRIBUTES attr;
830     UNICODE_STRING nameW;
831     WCHAR *devnameW;
832     char tmp[128];
833     char devname[40];
834
835     static const WCHAR serialportsW[] = {'M','a','c','h','i','n','e','\\',
836                                          'S','o','f','t','w','a','r','e','\\',
837                                          'W','i','n','e','\\','W','i','n','e','\\',
838                                          'C','o','n','f','i','g','\\',
839                                          'S','e','r','i','a','l','P','o','r','t','s',0};
840
841     TRACE_(file)("%s %lx %lx\n", debugstr_w(name), access, attributes);
842
843     attr.Length = sizeof(attr);
844     attr.RootDirectory = 0;
845     attr.ObjectName = &nameW;
846     attr.Attributes = 0;
847     attr.SecurityDescriptor = NULL;
848     attr.SecurityQualityOfService = NULL;
849     RtlInitUnicodeString( &nameW, serialportsW );
850
851     if (NtOpenKey( &hkey, KEY_ALL_ACCESS, &attr )) return 0;
852
853     RtlInitUnicodeString( &nameW, name );
854     if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
855         devnameW = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
856     else
857         devnameW = NULL;
858
859     NtClose( hkey );
860
861     if (!devnameW) return 0;
862     WideCharToMultiByte(CP_ACP, 0, devnameW, -1, devname, sizeof(devname), NULL, NULL);
863
864     TRACE("opening %s as %s\n", devname, debugstr_w(name));
865
866     SERVER_START_REQ( create_serial )
867     {
868         req->access  = access;
869         req->inherit = (sa && (sa->nLength>=sizeof(*sa)) && sa->bInheritHandle);
870         req->attributes = attributes;
871         req->sharing = FILE_SHARE_READ|FILE_SHARE_WRITE;
872         wine_server_add_data( req, devname, strlen(devname) );
873         SetLastError(0);
874         wine_server_call_err( req );
875         ret = reply->handle;
876     }
877     SERVER_END_REQ;
878
879     if(!ret)
880         ERR("Couldn't open device '%s' ! (check permissions)\n",devname);
881     else
882         TRACE("return %p\n", ret );
883     return ret;
884 }
885
886 /***********************************************************************
887  *           DOSFS_OpenDevice
888  *
889  * Open a DOS device. This might not map 1:1 into the UNIX device concept.
890  * Returns 0 on failure.
891  */
892 HANDLE DOSFS_OpenDevice( LPCWSTR name, DWORD access, DWORD attributes, LPSECURITY_ATTRIBUTES sa )
893 {
894     unsigned int i;
895     const WCHAR *p;
896     HANDLE handle;
897
898     if (name[0] && (name[1] == ':')) name += 2;
899     if ((p = strrchrW( name, '/' ))) name = p + 1;
900     if ((p = strrchrW( name, '\\' ))) name = p + 1;
901     for (i = 0; i < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0]); i++)
902     {
903         const WCHAR *dev = DOSFS_Devices[i].name;
904         if (!strncmpiW( dev, name, strlenW(dev) ))
905         {
906             p = name + strlenW( dev );
907             if (!*p || (*p == '.') || (*p == ':')) {
908                 static const WCHAR nulW[] = {'N','U','L',0};
909                 static const WCHAR conW[] = {'C','O','N',0};
910                 /* got it */
911                 if (!strcmpiW(DOSFS_Devices[i].name, nulW))
912                     return FILE_CreateFile( "/dev/null", access,
913                                             FILE_SHARE_READ|FILE_SHARE_WRITE, sa,
914                                             OPEN_EXISTING, 0, 0, TRUE, DRIVE_UNKNOWN );
915                 if (!strcmpiW(DOSFS_Devices[i].name, conW)) {
916                         HANDLE to_dup;
917                         switch (access & (GENERIC_READ|GENERIC_WRITE)) {
918                         case GENERIC_READ:
919                                 to_dup = GetStdHandle( STD_INPUT_HANDLE );
920                                 break;
921                         case GENERIC_WRITE:
922                                 to_dup = GetStdHandle( STD_OUTPUT_HANDLE );
923                                 break;
924                         default:
925                                 FIXME("can't open CON read/write\n");
926                                 return 0;
927                         }
928                         if (!DuplicateHandle( GetCurrentProcess(), to_dup, GetCurrentProcess(),
929                                               &handle, 0,
930                                               sa && (sa->nLength>=sizeof(*sa)) && sa->bInheritHandle,
931                                               DUPLICATE_SAME_ACCESS ))
932                             handle = 0;
933                         return handle;
934                 }
935
936                 if( (handle=DOSFS_CreateCommPort(DOSFS_Devices[i].name,access,attributes,sa)) )
937                     return handle;
938                 FIXME("device open %s not supported (yet)\n", debugstr_w(DOSFS_Devices[i].name));
939                 return 0;
940             }
941         }
942     }
943     return 0;
944 }
945
946
947 /***********************************************************************
948  *           DOSFS_GetPathDrive
949  *
950  * Get the drive specified by a given path name (DOS or Unix format).
951  */
952 static int DOSFS_GetPathDrive( LPCWSTR *name )
953 {
954     int drive;
955     LPCWSTR p = *name;
956
957     if (*p && (p[1] == ':'))
958     {
959         drive = toupperW(*p) - 'A';
960         *name += 2;
961     }
962     else if (*p == '/') /* Absolute Unix path? */
963     {
964         if ((drive = DRIVE_FindDriveRootW( name )) == -1)
965         {
966             MESSAGE("Warning: %s not accessible from a configured DOS drive\n", debugstr_w(*name) );
967             /* Assume it really was a DOS name */
968             drive = DRIVE_GetCurrentDrive();
969         }
970     }
971     else drive = DRIVE_GetCurrentDrive();
972
973     if (!DRIVE_IsValid(drive))
974     {
975         SetLastError( ERROR_INVALID_DRIVE );
976         return -1;
977     }
978     return drive;
979 }
980
981
982 /***********************************************************************
983  *           DOSFS_GetFullName
984  *
985  * Convert a file name (DOS or mixed DOS/Unix format) to a valid
986  * Unix name / short DOS name pair.
987  * Return FALSE if one of the path components does not exist. The last path
988  * component is only checked if 'check_last' is non-zero.
989  * The buffers pointed to by 'long_buf' and 'short_buf' must be
990  * at least MAX_PATHNAME_LEN long.
991  */
992 BOOL DOSFS_GetFullName( LPCWSTR name, BOOL check_last, DOS_FULL_NAME *full )
993 {
994     BOOL found;
995     char *p_l, *root;
996     LPWSTR p_s;
997     static const WCHAR driveA_rootW[] = {'A',':','\\',0};
998     static const WCHAR dos_rootW[] = {'\\',0};
999
1000     TRACE("%s (last=%d)\n", debugstr_w(name), check_last );
1001
1002     if ((!*name) || (*name=='\n'))
1003     { /* error code for Win98 */
1004         SetLastError(ERROR_BAD_PATHNAME);
1005         return FALSE;
1006     }
1007
1008     if ((full->drive = DOSFS_GetPathDrive( &name )) == -1) return FALSE;
1009
1010     lstrcpynA( full->long_name, DRIVE_GetRoot( full->drive ),
1011                  sizeof(full->long_name) );
1012     if (full->long_name[1]) root = full->long_name + strlen(full->long_name);
1013     else root = full->long_name;  /* root directory */
1014
1015     strcpyW( full->short_name, driveA_rootW );
1016     full->short_name[0] += full->drive;
1017
1018     if ((*name == '\\') || (*name == '/'))  /* Absolute path */
1019     {
1020         while ((*name == '\\') || (*name == '/')) name++;
1021     }
1022     else  /* Relative path */
1023     {
1024         lstrcpynA( root + 1, DRIVE_GetUnixCwd( full->drive ),
1025                      sizeof(full->long_name) - (root - full->long_name) - 1 );
1026         if (root[1]) *root = '/';
1027         lstrcpynW( full->short_name + 3, DRIVE_GetDosCwd( full->drive ),
1028                    sizeof(full->short_name)/sizeof(full->short_name[0]) - 3 );
1029     }
1030
1031     p_l = full->long_name[1] ? full->long_name + strlen(full->long_name)
1032                              : full->long_name;
1033     p_s = full->short_name[3] ? full->short_name + strlenW(full->short_name)
1034                               : full->short_name + 2;
1035     found = TRUE;
1036
1037     while (*name && found)
1038     {
1039         /* Check for '.' and '..' */
1040
1041         if (*name == '.')
1042         {
1043             if (is_end_of_name(name[1]))
1044             {
1045                 name++;
1046                 while ((*name == '\\') || (*name == '/')) name++;
1047                 continue;
1048             }
1049             else if ((name[1] == '.') && is_end_of_name(name[2]))
1050             {
1051                 name += 2;
1052                 while ((*name == '\\') || (*name == '/')) name++;
1053                 while ((p_l > root) && (*p_l != '/')) p_l--;
1054                 while ((p_s > full->short_name + 2) && (*p_s != '\\')) p_s--;
1055                 *p_l = *p_s = '\0';  /* Remove trailing separator */
1056                 continue;
1057             }
1058         }
1059
1060         /* Make sure buffers are large enough */
1061
1062         if ((p_s >= full->short_name + sizeof(full->short_name)/sizeof(full->short_name[0]) - 14) ||
1063             (p_l >= full->long_name + sizeof(full->long_name) - 1))
1064         {
1065             SetLastError( ERROR_PATH_NOT_FOUND );
1066             return FALSE;
1067         }
1068
1069         /* Get the long and short name matching the file name */
1070
1071         if ((found = DOSFS_FindUnixName( full, name, p_l + 1,
1072                          sizeof(full->long_name) - (p_l - full->long_name) - 1, p_s + 1 )))
1073         {
1074             *p_l++ = '/';
1075             p_l   += strlen(p_l);
1076             *p_s++ = '\\';
1077             p_s   += strlenW(p_s);
1078             while (!is_end_of_name(*name)) name++;
1079         }
1080         else if (!check_last)
1081         {
1082             *p_l++ = '/';
1083             *p_s++ = '\\';
1084             while (!is_end_of_name(*name) &&
1085                    (p_s < full->short_name + sizeof(full->short_name)/sizeof(full->short_name[0]) - 1) &&
1086                    (p_l < full->long_name + sizeof(full->long_name) - 1))
1087             {
1088                 WCHAR wch;
1089                 *p_s++ = tolowerW(*name);
1090                 /* If the drive is case-sensitive we want to create new */
1091                 /* files in lower-case otherwise we can't reopen them   */
1092                 /* under the same short name. */
1093                 if (is_case_sensitive) wch = tolowerW(*name);
1094                 else wch = *name;
1095                 p_l += WideCharToMultiByte(CP_UNIXCP, 0, &wch, 1, p_l, 2, NULL, NULL);
1096                 name++;
1097             }
1098             /* Ignore trailing dots and spaces */
1099             while(p_l[-1] == '.' || p_l[-1] == ' ') {
1100                 --p_l;
1101                 --p_s;
1102             }
1103             *p_l = '\0';
1104             *p_s = '\0';
1105         }
1106         while ((*name == '\\') || (*name == '/')) name++;
1107     }
1108
1109     if (!found)
1110     {
1111         if (check_last)
1112         {
1113             SetLastError( ERROR_FILE_NOT_FOUND );
1114             return FALSE;
1115         }
1116         if (*name)  /* Not last */
1117         {
1118             SetLastError( ERROR_PATH_NOT_FOUND );
1119             return FALSE;
1120         }
1121     }
1122     if (!full->long_name[0]) strcpy( full->long_name, "/" );
1123     if (!full->short_name[2]) strcpyW( full->short_name + 2, dos_rootW );
1124     TRACE("returning %s = %s\n", full->long_name, debugstr_w(full->short_name) );
1125     return TRUE;
1126 }
1127
1128
1129 /***********************************************************************
1130  *           wine_get_unix_file_name (KERNEL32.@) Not a Windows API
1131  *
1132  * Return the full Unix file name for a given path.
1133  */
1134 BOOL WINAPI wine_get_unix_file_name( LPCWSTR dosW, LPSTR buffer, DWORD len )
1135 {
1136     BOOL ret;
1137     DOS_FULL_NAME path;
1138
1139     ret = DOSFS_GetFullName( dosW, FALSE, &path );
1140     if (ret && len)
1141     {
1142         strncpy( buffer, path.long_name, len );
1143         buffer[len - 1] = 0; /* ensure 0 termination */
1144     }
1145     return ret;
1146 }
1147
1148
1149 /***********************************************************************
1150  *           get_show_dir_symlinks_option
1151  */
1152 static BOOL get_show_dir_symlinks_option(void)
1153 {
1154     static const WCHAR WineW[] = {'M','a','c','h','i','n','e','\\',
1155                                   'S','o','f','t','w','a','r','e','\\',
1156                                   'W','i','n','e','\\','W','i','n','e','\\',
1157                                   'C','o','n','f','i','g','\\','W','i','n','e',0};
1158     static const WCHAR ShowDirSymlinksW[] = {'S','h','o','w','D','i','r','S','y','m','l','i','n','k','s',0};
1159
1160     char tmp[80];
1161     HKEY hkey;
1162     DWORD dummy;
1163     OBJECT_ATTRIBUTES attr;
1164     UNICODE_STRING nameW;
1165     BOOL ret = FALSE;
1166
1167     attr.Length = sizeof(attr);
1168     attr.RootDirectory = 0;
1169     attr.ObjectName = &nameW;
1170     attr.Attributes = 0;
1171     attr.SecurityDescriptor = NULL;
1172     attr.SecurityQualityOfService = NULL;
1173     RtlInitUnicodeString( &nameW, WineW );
1174
1175     if (!NtOpenKey( &hkey, KEY_ALL_ACCESS, &attr ))
1176     {
1177         RtlInitUnicodeString( &nameW, ShowDirSymlinksW );
1178         if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
1179         {
1180             WCHAR *str = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
1181             ret = IS_OPTION_TRUE( str[0] );
1182         }
1183         NtClose( hkey );
1184     }
1185     return ret;
1186 }
1187
1188
1189 /***********************************************************************
1190  *           DOSFS_FindNextEx
1191  */
1192 static int DOSFS_FindNextEx( FIND_FIRST_INFO *info, WIN32_FIND_DATAW *entry )
1193 {
1194     char *p, buffer[MAX_PATHNAME_LEN];
1195     const char *drive_path;
1196     int drive_root;
1197     LPCWSTR long_name, short_name;
1198     BY_HANDLE_FILE_INFORMATION fileinfo;
1199     BOOL is_symlink;
1200
1201     drive_path = info->path + strlen(DRIVE_GetRoot( info->drive ));
1202     while ((*drive_path == '/') || (*drive_path == '\\')) drive_path++;
1203     drive_root = !*drive_path;
1204
1205     lstrcpynA( buffer, info->path, sizeof(buffer) - 1 );
1206     strcat( buffer, "/" );
1207     p = buffer + strlen(buffer);
1208
1209     while (DOSFS_ReadDir( info->u.dos_dir, &long_name, &short_name ))
1210     {
1211         info->cur_pos++;
1212
1213         /* Don't return '.' and '..' in the root of the drive */
1214         if (drive_root && (long_name[0] == '.') &&
1215             (!long_name[1] || ((long_name[1] == '.') && !long_name[2])))
1216             continue;
1217
1218         /* Check the long mask */
1219
1220         if (info->long_mask && *info->long_mask)
1221         {
1222             if (!DOSFS_MatchLong( info->long_mask, long_name )) continue;
1223         }
1224
1225         /* Check the file attributes */
1226         WideCharToMultiByte(CP_UNIXCP, 0, long_name, -1,
1227                             p, sizeof(buffer) - (int)(p - buffer), NULL, NULL);
1228         if (!FILE_Stat( buffer, &fileinfo, &is_symlink ))
1229         {
1230             WARN("can't stat %s\n", buffer);
1231             continue;
1232         }
1233         if (is_symlink && (fileinfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
1234         {
1235             static int show_dir_symlinks = -1;
1236             if (show_dir_symlinks == -1)
1237                 show_dir_symlinks = get_show_dir_symlinks_option();
1238             if (!show_dir_symlinks) continue;
1239         }
1240
1241         /* We now have a matching entry; fill the result and return */
1242
1243         entry->dwFileAttributes = fileinfo.dwFileAttributes;
1244         entry->ftCreationTime   = fileinfo.ftCreationTime;
1245         entry->ftLastAccessTime = fileinfo.ftLastAccessTime;
1246         entry->ftLastWriteTime  = fileinfo.ftLastWriteTime;
1247         entry->nFileSizeHigh    = fileinfo.nFileSizeHigh;
1248         entry->nFileSizeLow     = fileinfo.nFileSizeLow;
1249
1250         if (short_name)
1251             DOSFS_ToDosDTAFormat( short_name, entry->cAlternateFileName );
1252         else
1253             DOSFS_Hash( long_name, entry->cAlternateFileName, FALSE );
1254
1255         lstrcpynW( entry->cFileName, long_name, sizeof(entry->cFileName)/sizeof(entry->cFileName[0]) );
1256         TRACE("returning %s (%s) %02lx %ld\n",
1257               debugstr_w(entry->cFileName), debugstr_w(entry->cAlternateFileName),
1258               entry->dwFileAttributes, entry->nFileSizeLow );
1259         return 1;
1260     }
1261     return 0;  /* End of directory */
1262 }
1263
1264 /*************************************************************************
1265  *           FindFirstFileExW  (KERNEL32.@)
1266  */
1267 HANDLE WINAPI FindFirstFileExW(
1268         LPCWSTR lpFileName,
1269         FINDEX_INFO_LEVELS fInfoLevelId,
1270         LPVOID lpFindFileData,
1271         FINDEX_SEARCH_OPS fSearchOp,
1272         LPVOID lpSearchFilter,
1273         DWORD dwAdditionalFlags)
1274 {
1275     FIND_FIRST_INFO *info;
1276
1277     if (!lpFileName)
1278     {
1279         SetLastError(ERROR_PATH_NOT_FOUND);
1280         return INVALID_HANDLE_VALUE;
1281     }
1282
1283     if ((fSearchOp != FindExSearchNameMatch) || (dwAdditionalFlags != 0))
1284     {
1285         FIXME("options not implemented 0x%08x 0x%08lx\n", fSearchOp, dwAdditionalFlags );
1286         return INVALID_HANDLE_VALUE;
1287     }
1288
1289     switch(fInfoLevelId)
1290     {
1291       case FindExInfoStandard:
1292         {
1293           WIN32_FIND_DATAW * data = (WIN32_FIND_DATAW *) lpFindFileData;
1294           char *p;
1295           INT long_mask_len;
1296
1297           data->dwReserved0 = data->dwReserved1 = 0x0;
1298           if (lpFileName[0] == '\\' && lpFileName[1] == '\\')
1299           {
1300               ERR("UNC path name\n");
1301               if (!(info = HeapAlloc( GetProcessHeap(), 0, sizeof(FIND_FIRST_INFO)))) break;
1302               info->u.smb_dir = SMB_FindFirst(lpFileName);
1303               if(!info->u.smb_dir)
1304               {
1305                  HeapFree(GetProcessHeap(), 0, info);
1306                  break;
1307               }
1308               info->drive = -1;
1309               RtlInitializeCriticalSection( &info->cs );
1310           }
1311           else
1312           {
1313             DOS_FULL_NAME full_name;
1314
1315             if (lpFileName[0] && lpFileName[1] == ':')
1316             {
1317                 /* don't allow root directories */
1318                 if (!lpFileName[2] ||
1319                     ((lpFileName[2] == '/' || lpFileName[2] == '\\') && !lpFileName[3]))
1320                 {
1321                     SetLastError(ERROR_FILE_NOT_FOUND);
1322                     return INVALID_HANDLE_VALUE;
1323                 }
1324             }
1325             if (!DOSFS_GetFullName( lpFileName, FALSE, &full_name )) break;
1326             if (!(info = HeapAlloc( GetProcessHeap(), 0, sizeof(FIND_FIRST_INFO)))) break;
1327             RtlInitializeCriticalSection( &info->cs );
1328             info->path = HeapAlloc( GetProcessHeap(), 0, strlen(full_name.long_name)+1 );
1329             strcpy( info->path, full_name.long_name );
1330
1331             p = strrchr( info->path, '/' );
1332             *p++ = '\0';
1333             long_mask_len = MultiByteToWideChar(CP_UNIXCP, 0, p, -1, NULL, 0);
1334             info->long_mask = HeapAlloc( GetProcessHeap(), 0, long_mask_len * sizeof(WCHAR) );
1335             MultiByteToWideChar(CP_UNIXCP, 0, p, -1, info->long_mask, long_mask_len);
1336
1337             info->drive = full_name.drive;
1338             info->cur_pos = 0;
1339
1340             info->u.dos_dir = DOSFS_OpenDir( info->path );
1341           }
1342           if (!FindNextFileW( (HANDLE) info, data ))
1343           {
1344               FindClose( (HANDLE) info );
1345               SetLastError( ERROR_FILE_NOT_FOUND );
1346               break;
1347           }
1348           return (HANDLE) info;
1349         }
1350         break;
1351       default:
1352         FIXME("fInfoLevelId 0x%08x not implemented\n", fInfoLevelId );
1353     }
1354     return INVALID_HANDLE_VALUE;
1355 }
1356
1357 /*************************************************************************
1358  *           FindFirstFileA   (KERNEL32.@)
1359  */
1360 HANDLE WINAPI FindFirstFileA(
1361         LPCSTR lpFileName,
1362         WIN32_FIND_DATAA *lpFindData )
1363 {
1364     return FindFirstFileExA(lpFileName, FindExInfoStandard, lpFindData,
1365                             FindExSearchNameMatch, NULL, 0);
1366 }
1367
1368 /*************************************************************************
1369  *           FindFirstFileExA   (KERNEL32.@)
1370  */
1371 HANDLE WINAPI FindFirstFileExA(
1372         LPCSTR lpFileName,
1373         FINDEX_INFO_LEVELS fInfoLevelId,
1374         LPVOID lpFindFileData,
1375         FINDEX_SEARCH_OPS fSearchOp,
1376         LPVOID lpSearchFilter,
1377         DWORD dwAdditionalFlags)
1378 {
1379     HANDLE handle;
1380     WIN32_FIND_DATAA *dataA;
1381     WIN32_FIND_DATAW dataW;
1382     UNICODE_STRING pathW;
1383
1384     if (!lpFileName)
1385     {
1386         SetLastError(ERROR_PATH_NOT_FOUND);
1387         return INVALID_HANDLE_VALUE;
1388     }
1389
1390     if (!RtlCreateUnicodeStringFromAsciiz(&pathW, lpFileName))
1391     {
1392         SetLastError(ERROR_NOT_ENOUGH_MEMORY);
1393         return INVALID_HANDLE_VALUE;
1394     }
1395
1396     handle = FindFirstFileExW(pathW.Buffer, fInfoLevelId, &dataW, fSearchOp, lpSearchFilter, dwAdditionalFlags);
1397     RtlFreeUnicodeString(&pathW);
1398     if (handle == INVALID_HANDLE_VALUE) return handle;
1399
1400     dataA = (WIN32_FIND_DATAA *) lpFindFileData;
1401     dataA->dwFileAttributes = dataW.dwFileAttributes;
1402     dataA->ftCreationTime   = dataW.ftCreationTime;
1403     dataA->ftLastAccessTime = dataW.ftLastAccessTime;
1404     dataA->ftLastWriteTime  = dataW.ftLastWriteTime;
1405     dataA->nFileSizeHigh    = dataW.nFileSizeHigh;
1406     dataA->nFileSizeLow     = dataW.nFileSizeLow;
1407     WideCharToMultiByte( CP_ACP, 0, dataW.cFileName, -1,
1408                          dataA->cFileName, sizeof(dataA->cFileName), NULL, NULL );
1409     WideCharToMultiByte( CP_ACP, 0, dataW.cAlternateFileName, -1,
1410                          dataA->cAlternateFileName, sizeof(dataA->cAlternateFileName), NULL, NULL );
1411     return handle;
1412 }
1413
1414 /*************************************************************************
1415  *           FindFirstFileW   (KERNEL32.@)
1416  */
1417 HANDLE WINAPI FindFirstFileW( LPCWSTR lpFileName, WIN32_FIND_DATAW *lpFindData )
1418 {
1419     return FindFirstFileExW(lpFileName, FindExInfoStandard, lpFindData,
1420                             FindExSearchNameMatch, NULL, 0);
1421 }
1422
1423 /*************************************************************************
1424  *           FindNextFileW   (KERNEL32.@)
1425  */
1426 BOOL WINAPI FindNextFileW( HANDLE handle, WIN32_FIND_DATAW *data )
1427 {
1428     FIND_FIRST_INFO *info;
1429     BOOL ret = FALSE;
1430     DWORD gle = ERROR_NO_MORE_FILES;
1431
1432     if (handle == INVALID_HANDLE_VALUE)
1433     {
1434         SetLastError( ERROR_INVALID_HANDLE );
1435         return ret;
1436     }
1437     info = (FIND_FIRST_INFO*) handle;
1438     RtlEnterCriticalSection( &info->cs );
1439     if (info->drive == -1)
1440     {
1441         ret = SMB_FindNext( info->u.smb_dir, data );
1442         if(!ret)
1443         {
1444             SMB_CloseDir( info->u.smb_dir );
1445             HeapFree( GetProcessHeap(), 0, info->path );
1446         }
1447         goto done;
1448     }
1449     else if (!info->path || !info->u.dos_dir)
1450     {
1451         goto done;
1452     }
1453     else if (!DOSFS_FindNextEx( info, data ))
1454     {
1455         DOSFS_CloseDir( info->u.dos_dir ); info->u.dos_dir = NULL;
1456         HeapFree( GetProcessHeap(), 0, info->path );
1457         info->path = NULL;
1458         HeapFree( GetProcessHeap(), 0, info->long_mask );
1459         info->long_mask = NULL;
1460         goto done;
1461     }
1462     ret = TRUE;
1463 done:
1464     RtlLeaveCriticalSection( &info->cs );
1465     if( !ret ) SetLastError( gle );
1466     return ret;
1467 }
1468
1469
1470 /*************************************************************************
1471  *           FindNextFileA   (KERNEL32.@)
1472  */
1473 BOOL WINAPI FindNextFileA( HANDLE handle, WIN32_FIND_DATAA *data )
1474 {
1475     WIN32_FIND_DATAW dataW;
1476     if (!FindNextFileW( handle, &dataW )) return FALSE;
1477     data->dwFileAttributes = dataW.dwFileAttributes;
1478     data->ftCreationTime   = dataW.ftCreationTime;
1479     data->ftLastAccessTime = dataW.ftLastAccessTime;
1480     data->ftLastWriteTime  = dataW.ftLastWriteTime;
1481     data->nFileSizeHigh    = dataW.nFileSizeHigh;
1482     data->nFileSizeLow     = dataW.nFileSizeLow;
1483     WideCharToMultiByte( CP_ACP, 0, dataW.cFileName, -1,
1484                          data->cFileName, sizeof(data->cFileName), NULL, NULL );
1485     WideCharToMultiByte( CP_ACP, 0, dataW.cAlternateFileName, -1,
1486                          data->cAlternateFileName,
1487                          sizeof(data->cAlternateFileName), NULL, NULL );
1488     return TRUE;
1489 }
1490
1491 /*************************************************************************
1492  *           FindClose   (KERNEL32.@)
1493  */
1494 BOOL WINAPI FindClose( HANDLE handle )
1495 {
1496     FIND_FIRST_INFO *info = (FIND_FIRST_INFO*) handle;
1497
1498     if (handle == INVALID_HANDLE_VALUE) goto error;
1499
1500     __TRY
1501     {
1502         RtlEnterCriticalSection( &info->cs );
1503         if (info)
1504         {
1505             if (info->u.dos_dir) DOSFS_CloseDir( info->u.dos_dir );
1506             if (info->path) HeapFree( GetProcessHeap(), 0, info->path );
1507             if (info->long_mask) HeapFree( GetProcessHeap(), 0, info->long_mask );
1508         }
1509     }
1510     __EXCEPT(page_fault)
1511     {
1512         WARN("Illegal handle %p\n", handle);
1513         SetLastError( ERROR_INVALID_HANDLE );
1514         return FALSE;
1515     }
1516     __ENDTRY
1517     if (!info) goto error;
1518     RtlLeaveCriticalSection( &info->cs );
1519     RtlDeleteCriticalSection( &info->cs );
1520     HeapFree(GetProcessHeap(), 0, info);
1521     return TRUE;
1522
1523  error:
1524     SetLastError( ERROR_INVALID_HANDLE );
1525     return FALSE;
1526 }
1527
1528 /***********************************************************************
1529  *           MulDiv   (KERNEL32.@)
1530  * RETURNS
1531  *      Result of multiplication and division
1532  *      -1: Overflow occurred or Divisor was 0
1533  */
1534 INT WINAPI MulDiv(
1535              INT nMultiplicand,
1536              INT nMultiplier,
1537              INT nDivisor)
1538 {
1539     LONGLONG ret;
1540
1541     if (!nDivisor) return -1;
1542
1543     /* We want to deal with a positive divisor to simplify the logic. */
1544     if (nDivisor < 0)
1545     {
1546       nMultiplicand = - nMultiplicand;
1547       nDivisor = -nDivisor;
1548     }
1549
1550     /* If the result is positive, we "add" to round. else, we subtract to round. */
1551     if ( ( (nMultiplicand <  0) && (nMultiplier <  0) ) ||
1552          ( (nMultiplicand >= 0) && (nMultiplier >= 0) ) )
1553       ret = (((LONGLONG)nMultiplicand * nMultiplier) + (nDivisor/2)) / nDivisor;
1554     else
1555       ret = (((LONGLONG)nMultiplicand * nMultiplier) - (nDivisor/2)) / nDivisor;
1556
1557     if ((ret > 2147483647) || (ret < -2147483647)) return -1;
1558     return ret;
1559 }
1560
1561
1562 /***********************************************************************
1563  *           DosDateTimeToFileTime   (KERNEL32.@)
1564  */
1565 BOOL WINAPI DosDateTimeToFileTime( WORD fatdate, WORD fattime, LPFILETIME ft)
1566 {
1567     struct tm newtm;
1568 #ifndef HAVE_TIMEGM
1569     struct tm *gtm;
1570     time_t time1, time2;
1571 #endif
1572
1573     newtm.tm_sec  = (fattime & 0x1f) * 2;
1574     newtm.tm_min  = (fattime >> 5) & 0x3f;
1575     newtm.tm_hour = (fattime >> 11);
1576     newtm.tm_mday = (fatdate & 0x1f);
1577     newtm.tm_mon  = ((fatdate >> 5) & 0x0f) - 1;
1578     newtm.tm_year = (fatdate >> 9) + 80;
1579 #ifdef HAVE_TIMEGM
1580     RtlSecondsSince1970ToTime( timegm(&newtm), (LARGE_INTEGER *)ft );
1581 #else
1582     time1 = mktime(&newtm);
1583     gtm = gmtime(&time1);
1584     time2 = mktime(gtm);
1585     RtlSecondsSince1970ToTime( 2*time1-time2, (LARGE_INTEGER *)ft );
1586 #endif
1587     return TRUE;
1588 }
1589
1590
1591 /***********************************************************************
1592  *           FileTimeToDosDateTime   (KERNEL32.@)
1593  */
1594 BOOL WINAPI FileTimeToDosDateTime( const FILETIME *ft, LPWORD fatdate,
1595                                      LPWORD fattime )
1596 {
1597     LARGE_INTEGER       li;
1598     ULONG               t;
1599     time_t              unixtime;
1600     struct tm*          tm;
1601
1602     li.u.LowPart = ft->dwLowDateTime;
1603     li.u.HighPart = ft->dwHighDateTime;
1604     RtlTimeToSecondsSince1970( &li, &t );
1605     unixtime = t;
1606     tm = gmtime( &unixtime );
1607     if (fattime)
1608         *fattime = (tm->tm_hour << 11) + (tm->tm_min << 5) + (tm->tm_sec / 2);
1609     if (fatdate)
1610         *fatdate = ((tm->tm_year - 80) << 9) + ((tm->tm_mon + 1) << 5)
1611                    + tm->tm_mday;
1612     return TRUE;
1613 }
1614
1615
1616 /***********************************************************************
1617  *           QueryDosDeviceA   (KERNEL32.@)
1618  *
1619  * returns array of strings terminated by \0, terminated by \0
1620  */
1621 DWORD WINAPI QueryDosDeviceA(LPCSTR devname,LPSTR target,DWORD bufsize)
1622 {
1623     DWORD ret = 0, retW;
1624     LPWSTR targetW = (LPWSTR)HeapAlloc(GetProcessHeap(),0,
1625                                      bufsize * sizeof(WCHAR));
1626     UNICODE_STRING devnameW;
1627
1628     if(devname) RtlCreateUnicodeStringFromAsciiz(&devnameW, devname);
1629     else devnameW.Buffer = NULL;
1630
1631     retW = QueryDosDeviceW(devnameW.Buffer, targetW, bufsize);
1632
1633     ret = WideCharToMultiByte(CP_ACP, 0, targetW, retW, target,
1634                                         bufsize, NULL, NULL);
1635
1636     RtlFreeUnicodeString(&devnameW);
1637     if (targetW) HeapFree(GetProcessHeap(),0,targetW);
1638     return ret;
1639 }
1640
1641
1642 /***********************************************************************
1643  *           QueryDosDeviceW   (KERNEL32.@)
1644  *
1645  * returns array of strings terminated by \0, terminated by \0
1646  *
1647  * FIXME
1648  *      - Win9x returns for all calls ERROR_INVALID_PARAMETER 
1649  *      - the returned devices for devname == NULL is far from complete
1650  *      - its not checked that the returned device exist
1651  */
1652 DWORD WINAPI QueryDosDeviceW(LPCWSTR devname,LPWSTR target,DWORD bufsize)
1653 {
1654     const WCHAR *pDev, *pName, *pNum = NULL;
1655     int    numsiz=0;
1656     DWORD  ret;
1657
1658     TRACE("(%s,...)\n", debugstr_w(devname));
1659     if (!devname) {
1660         /* return known MSDOS devices */
1661         DWORD ret = 0;
1662         int i;
1663         static const WCHAR devices[][5] = {{'A','U','X',0},
1664                                            {'C','O','M','1',0},
1665                                            {'C','O','M','2',0},
1666                                            {'L','P','T','1',0},
1667                                            {'N','U','L',0,}};
1668         for(i=0; (i< (sizeof(devices)/sizeof(devices[0]))); i++) {
1669             DWORD len = strlenW(devices[i]);
1670             if(target && (bufsize >= ret + len + 2)) {
1671                 strcpyW(target+ret, devices[i]);
1672                 ret += len + 1;
1673             } else {
1674                 /* in this case WinXP returns 0 */
1675                 FIXME("function return is wrong for WinXP!\n");
1676                 SetLastError(ERROR_INSUFFICIENT_BUFFER);
1677                 break;
1678             }
1679         }
1680         /* append drives here */
1681         if(target && bufsize > 0) target[ret++] = 0;
1682         FIXME("Returned list is not complete\n");
1683         return ret;
1684     }
1685     /* In theory all that are possible and have been defined.
1686      * Now just those below, since mirc uses it to check for special files.
1687      *
1688      * (It is more complex, and supports netmounted stuff, and \\.\ stuff,
1689      *  but currently we just ignore that.)
1690      */
1691     if (!strcmpiW(devname, auxW)) {
1692         pDev   = dosW;
1693         pName  = comW;
1694         numsiz = 1;
1695         pNum   = oneW;
1696     } else if (!strcmpiW(devname, nulW)) {
1697         pDev  = devW;
1698         pName = nullW;
1699     } else if (!strncmpiW(devname, comW, strlenW(comW))) {
1700         pDev  = devW;
1701         pName = serW;
1702         pNum  = devname + strlenW(comW);
1703         for(numsiz=0; isdigitW(*(pNum+numsiz)); numsiz++);
1704         if(*(pNum + numsiz)) {
1705             SetLastError(ERROR_FILE_NOT_FOUND);
1706             return 0;
1707         }
1708     } else if (!strncmpiW(devname, lptW, strlenW(lptW))) {
1709         pDev  = devW;
1710         pName = parW;
1711         pNum  = devname + strlenW(lptW);
1712         for(numsiz=0; isdigitW(*(pNum+numsiz)); numsiz++);
1713         if(*(pNum + numsiz)) {
1714             SetLastError(ERROR_FILE_NOT_FOUND);
1715         return 0;
1716     }
1717     } else {
1718         /* This might be a DOS device we do not handle yet ... */
1719         FIXME("(%s) not detected as DOS device!\n",debugstr_w(devname));
1720
1721         /* Win9x set the error ERROR_INVALID_PARAMETER */
1722         SetLastError(ERROR_FILE_NOT_FOUND);
1723         return 0;
1724 }
1725     FIXME("device %s may not exist on this computer\n", debugstr_w(devname));
1726
1727     ret = strlenW(pDev) + strlenW(pName) + numsiz + 2;
1728     if (ret > bufsize) ret = 0;
1729     if (target && ret) {
1730         strcpyW(target,pDev);
1731         strcatW(target,pName);
1732         if (pNum) strcatW(target,pNum);
1733         target[ret-1] = 0;
1734     }
1735     return ret;
1736 }