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