Get rid of the drive codepages and use CP_UNIXCP instead.
[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
24 #include <sys/types.h>
25 #include <ctype.h>
26 #include <dirent.h>
27 #include <errno.h>
28 #ifdef HAVE_SYS_ERRNO_H
29 #include <sys/errno.h>
30 #endif
31 #include <fcntl.h>
32 #include <stdarg.h>
33 #include <string.h>
34 #include <stdlib.h>
35 #include <sys/stat.h>
36 #ifdef HAVE_SYS_IOCTL_H
37 #include <sys/ioctl.h>
38 #endif
39 #include <time.h>
40 #ifdef HAVE_UNISTD_H
41 # include <unistd.h>
42 #endif
43
44 #define NONAMELESSUNION
45 #define NONAMELESSSTRUCT
46 #include "ntstatus.h"
47 #include "windef.h"
48 #include "winbase.h"
49 #include "winerror.h"
50 #include "wingdi.h"
51
52 #include "wine/unicode.h"
53 #include "wine/winbase16.h"
54 #include "drive.h"
55 #include "file.h"
56 #include "heap.h"
57 #include "msdos.h"
58 #include "winternl.h"
59 #include "wine/server.h"
60 #include "excpt.h"
61
62 #include "smb.h"
63
64 #include "wine/debug.h"
65
66 WINE_DEFAULT_DEBUG_CHANNEL(dosfs);
67 WINE_DECLARE_DEBUG_CHANNEL(file);
68
69 /* Define the VFAT ioctl to get both short and long file names */
70 /* FIXME: is it possible to get this to work on other systems? */
71 #ifdef linux
72 /* We want the real kernel dirent structure, not the libc one */
73 typedef struct
74 {
75     long d_ino;
76     long d_off;
77     unsigned short d_reclen;
78     char d_name[256];
79 } KERNEL_DIRENT;
80
81 #define VFAT_IOCTL_READDIR_BOTH  _IOR('r', 1, KERNEL_DIRENT [2] )
82
83 /* To avoid blocking on non-directories in DOSFS_OpenDir_VFAT*/
84 #ifndef O_DIRECTORY
85 # define O_DIRECTORY    0200000 /* must be directory */
86 #endif
87
88 #else   /* linux */
89 #undef VFAT_IOCTL_READDIR_BOTH  /* just in case... */
90 #endif  /* linux */
91
92 #define IS_OPTION_TRUE(ch) ((ch) == 'y' || (ch) == 'Y' || (ch) == 't' || (ch) == 'T' || (ch) == '1')
93
94 /* Chars we don't want to see in DOS file names */
95 #define INVALID_DOS_CHARS  "*?<>|\"+=,;[] \345"
96
97 static const DOS_DEVICE DOSFS_Devices[] =
98 /* name, device flags (see Int 21/AX=0x4400) */
99 {
100     { {'C','O','N',0}, 0xc0d3 },
101     { {'P','R','N',0}, 0xa0c0 },
102     { {'N','U','L',0}, 0x80c4 },
103     { {'A','U','X',0}, 0x80c0 },
104     { {'L','P','T','1',0}, 0xa0c0 },
105     { {'L','P','T','2',0}, 0xa0c0 },
106     { {'L','P','T','3',0}, 0xa0c0 },
107     { {'L','P','T','4',0}, 0xc0d3 },
108     { {'C','O','M','1',0}, 0x80c0 },
109     { {'C','O','M','2',0}, 0x80c0 },
110     { {'C','O','M','3',0}, 0x80c0 },
111     { {'C','O','M','4',0}, 0x80c0 },
112     { {'S','C','S','I','M','G','R','$',0}, 0xc0c0 },
113     { {'H','P','S','C','A','N',0}, 0xc0c0 },
114     { {'E','M','M','X','X','X','X','0',0}, 0x0000 }
115 };
116
117 static const WCHAR devW[] = {'\\','D','e','v','i','c','e','\\',0};
118 static const WCHAR dosW[] = {'\\','D','o','s','D','e','v','i','c','e','s','\\',0};
119
120 static const WCHAR auxW[] = {'A','U','X',0};
121 static const WCHAR comW[] = {'C','O','M',0};
122 static const WCHAR lptW[] = {'L','P','T',0};
123 static const WCHAR nulW[] = {'N','U','L',0};
124
125 static const WCHAR nullW[] = {'N','u','l','l',0};
126 static const WCHAR parW[] = {'P','a','r','a','l','l','e','l',0};
127 static const WCHAR serW[] = {'S','e','r','i','a','l',0};
128 static const WCHAR oneW[] = {'1',0};
129
130 /*
131  * Directory info for DOSFS_ReadDir
132  * contains the names of *all* the files in the directory
133  */
134 typedef struct
135 {
136     int used;
137     int size;
138     WCHAR names[1];
139 } DOS_DIR;
140
141 /* Info structure for FindFirstFile handle */
142 typedef struct
143 {
144     char *path; /* unix path */
145     LPWSTR long_mask;
146     LPWSTR short_mask;
147     BYTE  attr;
148     int   drive;
149     int   cur_pos;
150     CRITICAL_SECTION cs;
151     union
152     {
153         DOS_DIR *dos_dir;
154         SMB_DIR *smb_dir;
155     } u;
156 } FIND_FIRST_INFO;
157
158
159 static WINE_EXCEPTION_FILTER(page_fault)
160 {
161     if (GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION)
162         return EXCEPTION_EXECUTE_HANDLER;
163     return EXCEPTION_CONTINUE_SEARCH;
164 }
165
166
167 /***********************************************************************
168  *           DOSFS_ValidDOSName
169  *
170  * Return 1 if Unix file 'name' is also a valid MS-DOS name
171  * (i.e. contains only valid DOS chars, lower-case only, fits in 8.3 format).
172  * File name can be terminated by '\0', '\\' or '/'.
173  */
174 static int DOSFS_ValidDOSName( LPCWSTR name, int ignore_case )
175 {
176     static const char invalid_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" INVALID_DOS_CHARS;
177     const WCHAR *p = name;
178     const char *invalid = ignore_case ? (invalid_chars + 26) : invalid_chars;
179     int len = 0;
180
181     if (*p == '.')
182     {
183         /* Check for "." and ".." */
184         p++;
185         if (*p == '.') p++;
186         /* All other names beginning with '.' are invalid */
187         return (IS_END_OF_NAME(*p));
188     }
189     while (!IS_END_OF_NAME(*p))
190     {
191         if (*p < 256 && strchr( invalid, (char)*p )) return 0;  /* Invalid char */
192         if (*p == '.') break;  /* Start of the extension */
193         if (++len > 8) return 0;  /* Name too long */
194         p++;
195     }
196     if (*p != '.') return 1;  /* End of name */
197     p++;
198     if (IS_END_OF_NAME(*p)) return 0;  /* Empty extension not allowed */
199     len = 0;
200     while (!IS_END_OF_NAME(*p))
201     {
202         if (*p < 256 && strchr( invalid, (char)*p )) return 0;  /* Invalid char */
203         if (*p == '.') return 0;  /* Second extension not allowed */
204         if (++len > 3) return 0;  /* Extension too long */
205         p++;
206     }
207     return 1;
208 }
209
210
211 /***********************************************************************
212  *           DOSFS_ToDosFCBFormat
213  *
214  * Convert a file name to DOS FCB format (8+3 chars, padded with blanks),
215  * expanding wild cards and converting to upper-case in the process.
216  * File name can be terminated by '\0', '\\' or '/'.
217  * Return FALSE if the name is not a valid DOS name.
218  * 'buffer' must be at least 12 characters long.
219  */
220 BOOL DOSFS_ToDosFCBFormat( LPCWSTR name, LPWSTR buffer )
221 {
222     static const char invalid_chars[] = INVALID_DOS_CHARS;
223     LPCWSTR p = name;
224     int i;
225
226     /* Check for "." and ".." */
227     if (*p == '.')
228     {
229         p++;
230         buffer[0] = '.';
231         for(i = 1; i < 11; i++) buffer[i] = ' ';
232         buffer[11] = 0;
233         if (*p == '.')
234         {
235             buffer[1] = '.';
236             p++;
237         }
238         return (!*p || (*p == '/') || (*p == '\\'));
239     }
240
241     for (i = 0; i < 8; i++)
242     {
243         switch(*p)
244         {
245         case '\0':
246         case '\\':
247         case '/':
248         case '.':
249             buffer[i] = ' ';
250             break;
251         case '?':
252             p++;
253             /* fall through */
254         case '*':
255             buffer[i] = '?';
256             break;
257         default:
258             if (*p < 256 && strchr( invalid_chars, (char)*p )) return FALSE;
259             buffer[i] = toupperW(*p);
260             p++;
261             break;
262         }
263     }
264
265     if (*p == '*')
266     {
267         /* Skip all chars after wildcard up to first dot */
268         while (*p && (*p != '/') && (*p != '\\') && (*p != '.')) p++;
269     }
270     else
271     {
272         /* Check if name too long */
273         if (*p && (*p != '/') && (*p != '\\') && (*p != '.')) return FALSE;
274     }
275     if (*p == '.') p++;  /* Skip dot */
276
277     for (i = 8; i < 11; i++)
278     {
279         switch(*p)
280         {
281         case '\0':
282         case '\\':
283         case '/':
284             buffer[i] = ' ';
285             break;
286         case '.':
287             return FALSE;  /* Second extension not allowed */
288         case '?':
289             p++;
290             /* fall through */
291         case '*':
292             buffer[i] = '?';
293             break;
294         default:
295             if (*p < 256 && strchr( invalid_chars, (char)*p )) return FALSE;
296             buffer[i] = toupperW(*p);
297             p++;
298             break;
299         }
300     }
301     buffer[11] = '\0';
302
303     /* at most 3 character of the extension are processed
304      * is something behind this ?
305      */
306     while (*p == '*' || *p == ' ') p++; /* skip wildcards and spaces */
307     return IS_END_OF_NAME(*p);
308 }
309
310
311 /***********************************************************************
312  *           DOSFS_ToDosDTAFormat
313  *
314  * Convert a file name from FCB to DTA format (name.ext, null-terminated)
315  * converting to upper-case in the process.
316  * File name can be terminated by '\0', '\\' or '/'.
317  * 'buffer' must be at least 13 characters long.
318  */
319 static void DOSFS_ToDosDTAFormat( LPCWSTR name, LPWSTR buffer )
320 {
321     LPWSTR p;
322
323     memcpy( buffer, name, 8 * sizeof(WCHAR) );
324     p = buffer + 8;
325     while ((p > buffer) && (p[-1] == ' ')) p--;
326     *p++ = '.';
327     memcpy( p, name + 8, 3 * sizeof(WCHAR) );
328     p += 3;
329     while (p[-1] == ' ') p--;
330     if (p[-1] == '.') p--;
331     *p = '\0';
332 }
333
334
335 /***********************************************************************
336  *           DOSFS_MatchShort
337  *
338  * Check a DOS file name against a mask (both in FCB format).
339  */
340 static int DOSFS_MatchShort( LPCWSTR mask, LPCWSTR name )
341 {
342     int i;
343     for (i = 11; i > 0; i--, mask++, name++)
344         if ((*mask != '?') && (*mask != *name)) return 0;
345     return 1;
346 }
347
348
349 /***********************************************************************
350  *           DOSFS_MatchLong
351  *
352  * Check a long file name against a mask.
353  *
354  * Tests (done in W95 DOS shell - case insensitive):
355  * *.txt                        test1.test.txt                          *
356  * *st1*                        test1.txt                               *
357  * *.t??????.t*                 test1.ta.tornado.txt                    *
358  * *tornado*                    test1.ta.tornado.txt                    *
359  * t*t                          test1.ta.tornado.txt                    *
360  * ?est*                        test1.txt                               *
361  * ?est???                      test1.txt                               -
362  * *test1.txt*                  test1.txt                               *
363  * h?l?o*t.dat                  hellothisisatest.dat                    *
364  */
365 static int DOSFS_MatchLong( LPCWSTR mask, LPCWSTR name, int case_sensitive )
366 {
367     LPCWSTR lastjoker = NULL;
368     LPCWSTR next_to_retry = NULL;
369     static const WCHAR asterisk_dot_asterisk[] = {'*','.','*',0};
370
371     TRACE("(%s, %s, %x)\n", debugstr_w(mask), debugstr_w(name), case_sensitive);
372
373     if (!strcmpW( mask, asterisk_dot_asterisk )) return 1;
374     while (*name && *mask)
375     {
376         if (*mask == '*')
377         {
378             mask++;
379             while (*mask == '*') mask++;  /* Skip consecutive '*' */
380             lastjoker = mask;
381             if (!*mask) return 1; /* end of mask is all '*', so match */
382
383             /* skip to the next match after the joker(s) */
384             if (case_sensitive) while (*name && (*name != *mask)) name++;
385             else while (*name && (toupperW(*name) != toupperW(*mask))) name++;
386
387             if (!*name) break;
388             next_to_retry = name;
389         }
390         else if (*mask != '?')
391         {
392             int mismatch = 0;
393             if (case_sensitive)
394             {
395                 if (*mask != *name) mismatch = 1;
396             }
397             else
398             {
399                 if (toupperW(*mask) != toupperW(*name)) mismatch = 1;
400             }
401             if (!mismatch)
402             {
403                 mask++;
404                 name++;
405                 if (*mask == '\0')
406                 {
407                     if (*name == '\0')
408                         return 1;
409                     if (lastjoker)
410                         mask = lastjoker;
411                 }
412             }
413             else /* mismatch ! */
414             {
415                 if (lastjoker) /* we had an '*', so we can try unlimitedly */
416                 {
417                     mask = lastjoker;
418
419                     /* this scan sequence was a mismatch, so restart
420                      * 1 char after the first char we checked last time */
421                     next_to_retry++;
422                     name = next_to_retry;
423                 }
424                 else
425                     return 0; /* bad luck */
426             }
427         }
428         else /* '?' */
429         {
430             mask++;
431             name++;
432         }
433     }
434     while ((*mask == '.') || (*mask == '*'))
435         mask++;  /* Ignore trailing '.' or '*' in mask */
436     return (!*name && !*mask);
437 }
438
439
440 /***********************************************************************
441  *           DOSFS_AddDirEntry
442  *
443  *  Used to construct an array of filenames in DOSFS_OpenDir
444  */
445 static BOOL DOSFS_AddDirEntry(DOS_DIR **dir, LPCWSTR name, LPCWSTR dosname)
446 {
447     int extra1 = strlenW(name) + 1;
448     int extra2 = strlenW(dosname) + 1;
449
450     /* if we need more, at minimum double the size */
451     if( (extra1 + extra2 + (*dir)->used) > (*dir)->size)
452     {
453         int more = (*dir)->size;
454         DOS_DIR *t;
455
456         if(more<(extra1+extra2))
457             more = extra1+extra2;
458
459         t = HeapReAlloc(GetProcessHeap(), 0, *dir, sizeof(**dir) + 
460                         ((*dir)->size + more)*sizeof(WCHAR) );
461         if(!t)
462         {
463             SetLastError( ERROR_NOT_ENOUGH_MEMORY );
464             ERR("Out of memory caching directory structure %d %d %d\n",
465                  (*dir)->size, more, (*dir)->used);
466             return FALSE;
467         }
468         (*dir) = t;
469         (*dir)->size += more;
470     }
471
472     /* at this point, the dir structure is big enough to hold these names */
473     strcpyW(&(*dir)->names[(*dir)->used], name);
474     (*dir)->used += extra1;
475     strcpyW(&(*dir)->names[(*dir)->used], dosname);
476     (*dir)->used += extra2;
477
478     return TRUE;
479 }
480
481
482 /***********************************************************************
483  *           DOSFS_OpenDir_VFAT
484  */
485 static BOOL DOSFS_OpenDir_VFAT(DOS_DIR **dir, const char *unix_path)
486 {
487 #ifdef VFAT_IOCTL_READDIR_BOTH
488     KERNEL_DIRENT de[2];
489     int fd = open( unix_path, O_RDONLY|O_DIRECTORY );
490     BOOL r = TRUE;
491
492     /* Check if the VFAT ioctl is supported on this directory */
493
494     if ( fd<0 )
495         return FALSE;
496
497     while (1)
498     {
499         WCHAR long_name[MAX_PATH];
500         WCHAR short_name[12];
501
502         r = (ioctl( fd, VFAT_IOCTL_READDIR_BOTH, (long)de ) != -1);
503         if(!r)
504             break;
505         if (!de[0].d_reclen)
506             break;
507         MultiByteToWideChar(CP_UNIXCP, 0, de[0].d_name, -1, long_name, MAX_PATH);
508         if (!DOSFS_ToDosFCBFormat( long_name, short_name ))
509             short_name[0] = '\0';
510         if (de[1].d_name[0])
511             MultiByteToWideChar(CP_UNIXCP, 0, de[1].d_name, -1, long_name, MAX_PATH);
512         else
513             MultiByteToWideChar(CP_UNIXCP, 0, de[0].d_name, -1, long_name, MAX_PATH);
514         r = DOSFS_AddDirEntry(dir, long_name, short_name );
515         if(!r)
516             break;
517     }
518     if(r)
519     {
520         static const WCHAR empty_strW[] = { 0 };
521         DOSFS_AddDirEntry(dir, empty_strW, empty_strW);
522     }
523     close(fd);
524     return r;
525 #else
526     return FALSE;
527 #endif  /* VFAT_IOCTL_READDIR_BOTH */
528 }
529
530
531 /***********************************************************************
532  *           DOSFS_OpenDir_Normal
533  *
534  * Now use the standard opendir/readdir interface
535  */
536 static BOOL DOSFS_OpenDir_Normal( DOS_DIR **dir, const char *unix_path )
537 {
538     DIR *unixdir = opendir( unix_path );
539     BOOL r = TRUE;
540     static const WCHAR empty_strW[] = { 0 };
541
542     if(!unixdir)
543         return FALSE;
544     while(1)
545     {
546         WCHAR long_name[MAX_PATH];
547         struct dirent *de = readdir(unixdir);
548
549         if(!de)
550             break;
551         MultiByteToWideChar(CP_UNIXCP, 0, de->d_name, -1, long_name, MAX_PATH);
552         r = DOSFS_AddDirEntry(dir, long_name, empty_strW);
553         if(!r)
554             break;
555     }
556     if(r)
557         DOSFS_AddDirEntry(dir, empty_strW, empty_strW);
558     closedir(unixdir);
559     return r;
560 }
561
562 /***********************************************************************
563  *           DOSFS_OpenDir
564  */
565 static DOS_DIR *DOSFS_OpenDir( const char *unix_path )
566 {
567     const int init_size = 0x100;
568     DOS_DIR *dir = HeapAlloc( GetProcessHeap(), 0, sizeof(*dir) + init_size*sizeof (WCHAR));
569     BOOL r;
570
571     TRACE("%s\n",debugstr_a(unix_path));
572
573     if (!dir)
574     {
575         SetLastError( ERROR_NOT_ENOUGH_MEMORY );
576         return NULL;
577     }
578     dir->used = 0;
579     dir->size = init_size;
580
581     /* Treat empty path as root directory. This simplifies path split into
582        directory and mask in several other places */
583     if (!*unix_path) unix_path = "/";
584
585     r = DOSFS_OpenDir_VFAT( &dir, unix_path);
586
587     if(!r)
588         r = DOSFS_OpenDir_Normal( &dir, unix_path);
589
590     if(!r)
591     {
592         HeapFree(GetProcessHeap(), 0, dir);
593         return NULL;
594     }
595     dir->used = 0;
596
597     return dir;
598 }
599
600
601 /***********************************************************************
602  *           DOSFS_CloseDir
603  */
604 static void DOSFS_CloseDir( DOS_DIR *dir )
605 {
606     HeapFree( GetProcessHeap(), 0, dir );
607 }
608
609
610 /***********************************************************************
611  *           DOSFS_ReadDir
612  */
613 static BOOL DOSFS_ReadDir( DOS_DIR *dir, LPCWSTR *long_name,
614                              LPCWSTR *short_name )
615 {
616     LPCWSTR sn, ln;
617
618     if (!dir)
619        return FALSE;
620
621     /* the long pathname is first */
622     ln = &dir->names[dir->used];
623     if(ln[0])
624         *long_name  = ln;
625     else
626         return FALSE;
627     dir->used += (strlenW(ln) + 1);
628
629     /* followed by the short path name */
630     sn = &dir->names[dir->used];
631     if(sn[0])
632         *short_name = sn;
633     else
634         *short_name = NULL;
635     dir->used += (strlenW(sn) + 1);
636
637     return TRUE;
638 }
639
640
641 /***********************************************************************
642  *           DOSFS_Hash
643  *
644  * Transform a Unix file name into a hashed DOS name. If the name is a valid
645  * DOS name, it is converted to upper-case; otherwise it is replaced by a
646  * hashed version that fits in 8.3 format.
647  * File name can be terminated by '\0', '\\' or '/'.
648  * 'buffer' must be at least 13 characters long.
649  */
650 static void DOSFS_Hash( LPCWSTR name, LPWSTR buffer, BOOL dir_format,
651                         BOOL ignore_case )
652 {
653     static const char invalid_chars[] = INVALID_DOS_CHARS "~.";
654     static const char hash_chars[32] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345";
655
656     LPCWSTR p, ext;
657     LPWSTR dst;
658     unsigned short hash;
659     int i;
660
661     if (dir_format)
662     {
663         for(i = 0; i < 11; i++) buffer[i] = ' ';
664         buffer[11] = 0;
665     }
666
667     if (DOSFS_ValidDOSName( name, ignore_case ))
668     {
669         /* Check for '.' and '..' */
670         if (*name == '.')
671         {
672             buffer[0] = '.';
673             if (!dir_format) buffer[1] = buffer[2] = '\0';
674             if (name[1] == '.') buffer[1] = '.';
675             return;
676         }
677
678         /* Simply copy the name, converting to uppercase */
679
680         for (dst = buffer; !IS_END_OF_NAME(*name) && (*name != '.'); name++)
681             *dst++ = toupperW(*name);
682         if (*name == '.')
683         {
684             if (dir_format) dst = buffer + 8;
685             else *dst++ = '.';
686             for (name++; !IS_END_OF_NAME(*name); name++)
687                 *dst++ = toupperW(*name);
688         }
689         if (!dir_format) *dst = '\0';
690         return;
691     }
692
693     /* Compute the hash code of the file name */
694     /* If you know something about hash functions, feel free to */
695     /* insert a better algorithm here... */
696     if (ignore_case)
697     {
698         for (p = name, hash = 0xbeef; !IS_END_OF_NAME(p[1]); p++)
699             hash = (hash<<3) ^ (hash>>5) ^ tolowerW(*p) ^ (tolowerW(p[1]) << 8);
700         hash = (hash<<3) ^ (hash>>5) ^ tolowerW(*p); /* Last character */
701     }
702     else
703     {
704         for (p = name, hash = 0xbeef; !IS_END_OF_NAME(p[1]); p++)
705             hash = (hash << 3) ^ (hash >> 5) ^ *p ^ (p[1] << 8);
706         hash = (hash << 3) ^ (hash >> 5) ^ *p;  /* Last character */
707     }
708
709     /* Find last dot for start of the extension */
710     for (p = name+1, ext = NULL; !IS_END_OF_NAME(*p); p++)
711         if (*p == '.') ext = p;
712     if (ext && IS_END_OF_NAME(ext[1]))
713         ext = NULL;  /* Empty extension ignored */
714
715     /* Copy first 4 chars, replacing invalid chars with '_' */
716     for (i = 4, p = name, dst = buffer; i > 0; i--, p++)
717     {
718         if (IS_END_OF_NAME(*p) || (p == ext)) break;
719         *dst++ = (*p < 256 && strchr( invalid_chars, (char)*p )) ? '_' : toupperW(*p);
720     }
721     /* Pad to 5 chars with '~' */
722     while (i-- >= 0) *dst++ = '~';
723
724     /* Insert hash code converted to 3 ASCII chars */
725     *dst++ = hash_chars[(hash >> 10) & 0x1f];
726     *dst++ = hash_chars[(hash >> 5) & 0x1f];
727     *dst++ = hash_chars[hash & 0x1f];
728
729     /* Copy the first 3 chars of the extension (if any) */
730     if (ext)
731     {
732         if (!dir_format) *dst++ = '.';
733         for (i = 3, ext++; (i > 0) && !IS_END_OF_NAME(*ext); i--, ext++)
734             *dst++ = (*ext < 256 && strchr( invalid_chars, (char)*ext )) ? '_' : toupperW(*ext);
735     }
736     if (!dir_format) *dst = '\0';
737 }
738
739
740 /***********************************************************************
741  *           DOSFS_FindUnixName
742  *
743  * Find the Unix file name in a given directory that corresponds to
744  * a file name (either in Unix or DOS format).
745  * File name can be terminated by '\0', '\\' or '/'.
746  * Return TRUE if OK, FALSE if no file name matches.
747  *
748  * 'long_buf' must be at least 'long_len' characters long. If the long name
749  * turns out to be larger than that, the function returns FALSE.
750  * 'short_buf' must be at least 13 characters long.
751  */
752 BOOL DOSFS_FindUnixName( const DOS_FULL_NAME *path, LPCWSTR name, char *long_buf,
753                          INT long_len, LPWSTR short_buf, BOOL ignore_case)
754 {
755     DOS_DIR *dir;
756     LPCWSTR long_name, short_name;
757     WCHAR dos_name[12], tmp_buf[13];
758     BOOL ret;
759
760     LPCWSTR p = strchrW( name, '/' );
761     int len = p ? (int)(p - name) : strlenW(name);
762     if ((p = strchrW( name, '\\' ))) len = min( (int)(p - name), len );
763     /* Ignore trailing dots and spaces */
764     while (len > 1 && (name[len-1] == '.' || name[len-1] == ' ')) len--;
765     if (long_len < len + 1) return FALSE;
766
767     TRACE("%s,%s\n", path->long_name, debugstr_w(name) );
768
769     if (!DOSFS_ToDosFCBFormat( name, dos_name )) dos_name[0] = '\0';
770
771     if (!(dir = DOSFS_OpenDir( path->long_name )))
772     {
773         WARN("(%s,%s): can't open dir: %s\n",
774              path->long_name, debugstr_w(name), strerror(errno) );
775         return FALSE;
776     }
777
778     while ((ret = DOSFS_ReadDir( dir, &long_name, &short_name )))
779     {
780         /* Check against Unix name */
781         if (len == strlenW(long_name))
782         {
783             if (!ignore_case)
784             {
785                 if (!strncmpW( long_name, name, len )) break;
786             }
787             else
788             {
789                 if (!strncmpiW( long_name, name, len )) break;
790             }
791         }
792         if (dos_name[0])
793         {
794             /* Check against hashed DOS name */
795             if (!short_name)
796             {
797                 DOSFS_Hash( long_name, tmp_buf, TRUE, ignore_case );
798                 short_name = tmp_buf;
799             }
800             if (!strcmpW( dos_name, short_name )) break;
801         }
802     }
803     if (ret)
804     {
805         if (long_buf) WideCharToMultiByte(CP_UNIXCP, 0, long_name, -1, long_buf, long_len, NULL, NULL);
806         if (short_buf)
807         {
808             if (short_name)
809                 DOSFS_ToDosDTAFormat( short_name, short_buf );
810             else
811                 DOSFS_Hash( long_name, short_buf, FALSE, ignore_case );
812         }
813         TRACE("(%s,%s) -> %s (%s)\n", path->long_name, debugstr_w(name),
814               debugstr_w(long_name), short_buf ? debugstr_w(short_buf) : "***");
815     }
816     else
817         WARN("%s not found in '%s'\n", debugstr_w(name), path->long_name);
818     DOSFS_CloseDir( dir );
819     return ret;
820 }
821
822
823 /***********************************************************************
824  *           DOSFS_GetDevice
825  *
826  * Check if a DOS file name represents a DOS device and return the device.
827  */
828 const DOS_DEVICE *DOSFS_GetDevice( LPCWSTR name )
829 {
830     unsigned int i;
831     const WCHAR *p;
832
833     if (!name) return NULL; /* if wine_server_handle_to_fd was used */
834     if (name[0] && (name[1] == ':')) name += 2;
835     if ((p = strrchrW( name, '/' ))) name = p + 1;
836     if ((p = strrchrW( name, '\\' ))) name = p + 1;
837     for (i = 0; i < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0]); i++)
838     {
839         const WCHAR *dev = DOSFS_Devices[i].name;
840         if (!strncmpiW( dev, name, strlenW(dev) ))
841         {
842             p = name + strlenW( dev );
843             if (!*p || (*p == '.') || (*p == ':')) return &DOSFS_Devices[i];
844         }
845     }
846     return NULL;
847 }
848
849
850 /***********************************************************************
851  *           DOSFS_GetDeviceByHandle
852  */
853 const DOS_DEVICE *DOSFS_GetDeviceByHandle( HANDLE hFile )
854 {
855     const DOS_DEVICE *ret = NULL;
856     SERVER_START_REQ( get_device_id )
857     {
858         req->handle = hFile;
859         if (!wine_server_call( req ))
860         {
861             if ((reply->id >= 0) &&
862                 (reply->id < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0])))
863                 ret = &DOSFS_Devices[reply->id];
864         }
865     }
866     SERVER_END_REQ;
867     return ret;
868 }
869
870
871 /**************************************************************************
872  *         DOSFS_CreateCommPort
873  */
874 static HANDLE DOSFS_CreateCommPort(LPCWSTR name, DWORD access, DWORD attributes, LPSECURITY_ATTRIBUTES sa)
875 {
876     HANDLE ret;
877     HKEY hkey;
878     DWORD dummy;
879     OBJECT_ATTRIBUTES attr;
880     UNICODE_STRING nameW;
881     WCHAR *devnameW;
882     char tmp[128];
883     char devname[40];
884
885     static const WCHAR serialportsW[] = {'M','a','c','h','i','n','e','\\',
886                                          'S','o','f','t','w','a','r','e','\\',
887                                          'W','i','n','e','\\','W','i','n','e','\\',
888                                          'C','o','n','f','i','g','\\',
889                                          'S','e','r','i','a','l','P','o','r','t','s',0};
890
891     TRACE_(file)("%s %lx %lx\n", debugstr_w(name), access, attributes);
892
893     attr.Length = sizeof(attr);
894     attr.RootDirectory = 0;
895     attr.ObjectName = &nameW;
896     attr.Attributes = 0;
897     attr.SecurityDescriptor = NULL;
898     attr.SecurityQualityOfService = NULL;
899     RtlInitUnicodeString( &nameW, serialportsW );
900
901     if (NtOpenKey( &hkey, KEY_ALL_ACCESS, &attr )) return 0;
902
903     RtlInitUnicodeString( &nameW, name );
904     if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
905         devnameW = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
906     else
907         devnameW = NULL;
908
909     NtClose( hkey );
910
911     if (!devnameW) return 0;
912     WideCharToMultiByte(CP_ACP, 0, devnameW, -1, devname, sizeof(devname), NULL, NULL);
913
914     TRACE("opening %s as %s\n", devname, debugstr_w(name));
915
916     SERVER_START_REQ( create_serial )
917     {
918         req->access  = access;
919         req->inherit = (sa && (sa->nLength>=sizeof(*sa)) && sa->bInheritHandle);
920         req->attributes = attributes;
921         req->sharing = FILE_SHARE_READ|FILE_SHARE_WRITE;
922         wine_server_add_data( req, devname, strlen(devname) );
923         SetLastError(0);
924         wine_server_call_err( req );
925         ret = reply->handle;
926     }
927     SERVER_END_REQ;
928
929     if(!ret)
930         ERR("Couldn't open device '%s' ! (check permissions)\n",devname);
931     else
932         TRACE("return %p\n", ret );
933     return ret;
934 }
935
936 /***********************************************************************
937  *           DOSFS_OpenDevice
938  *
939  * Open a DOS device. This might not map 1:1 into the UNIX device concept.
940  * Returns 0 on failure.
941  */
942 HANDLE DOSFS_OpenDevice( LPCWSTR name, DWORD access, DWORD attributes, LPSECURITY_ATTRIBUTES sa )
943 {
944     unsigned int i;
945     const WCHAR *p;
946     HANDLE handle;
947
948     if (name[0] && (name[1] == ':')) name += 2;
949     if ((p = strrchrW( name, '/' ))) name = p + 1;
950     if ((p = strrchrW( name, '\\' ))) name = p + 1;
951     for (i = 0; i < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0]); i++)
952     {
953         const WCHAR *dev = DOSFS_Devices[i].name;
954         if (!strncmpiW( dev, name, strlenW(dev) ))
955         {
956             p = name + strlenW( dev );
957             if (!*p || (*p == '.') || (*p == ':')) {
958                 static const WCHAR nulW[] = {'N','U','L',0};
959                 static const WCHAR conW[] = {'C','O','N',0};
960                 static const WCHAR scsimgrW[] = {'S','C','S','I','M','G','R','$',0};
961                 static const WCHAR hpscanW[] = {'H','P','S','C','A','N',0};
962                 static const WCHAR emmxxxx0W[] = {'E','M','M','X','X','X','X','0',0};
963                 /* got it */
964                 if (!strcmpiW(DOSFS_Devices[i].name, nulW))
965                     return FILE_CreateFile( "/dev/null", access,
966                                             FILE_SHARE_READ|FILE_SHARE_WRITE, sa,
967                                             OPEN_EXISTING, 0, 0, TRUE, DRIVE_UNKNOWN );
968                 if (!strcmpiW(DOSFS_Devices[i].name, conW)) {
969                         HANDLE to_dup;
970                         switch (access & (GENERIC_READ|GENERIC_WRITE)) {
971                         case GENERIC_READ:
972                                 to_dup = GetStdHandle( STD_INPUT_HANDLE );
973                                 break;
974                         case GENERIC_WRITE:
975                                 to_dup = GetStdHandle( STD_OUTPUT_HANDLE );
976                                 break;
977                         default:
978                                 FIXME("can't open CON read/write\n");
979                                 return 0;
980                         }
981                         if (!DuplicateHandle( GetCurrentProcess(), to_dup, GetCurrentProcess(),
982                                               &handle, 0,
983                                               sa && (sa->nLength>=sizeof(*sa)) && sa->bInheritHandle,
984                                               DUPLICATE_SAME_ACCESS ))
985                             handle = 0;
986                         return handle;
987                 }
988                 if (!strcmpiW(DOSFS_Devices[i].name, scsimgrW) ||
989                     !strcmpiW(DOSFS_Devices[i].name, hpscanW) ||
990                     !strcmpiW(DOSFS_Devices[i].name, emmxxxx0W))
991                 {
992                     return FILE_CreateDevice( i, access, sa );
993                 }
994
995                 if( (handle=DOSFS_CreateCommPort(DOSFS_Devices[i].name,access,attributes,sa)) )
996                     return handle;
997                 FIXME("device open %s not supported (yet)\n", debugstr_w(DOSFS_Devices[i].name));
998                 return 0;
999             }
1000         }
1001     }
1002     return 0;
1003 }
1004
1005
1006 /***********************************************************************
1007  *           DOSFS_GetPathDrive
1008  *
1009  * Get the drive specified by a given path name (DOS or Unix format).
1010  */
1011 static int DOSFS_GetPathDrive( LPCWSTR *name )
1012 {
1013     int drive;
1014     LPCWSTR p = *name;
1015
1016     if (*p && (p[1] == ':'))
1017     {
1018         drive = toupperW(*p) - 'A';
1019         *name += 2;
1020     }
1021     else if (*p == '/') /* Absolute Unix path? */
1022     {
1023         if ((drive = DRIVE_FindDriveRootW( name )) == -1)
1024         {
1025             MESSAGE("Warning: %s not accessible from a configured DOS drive\n", debugstr_w(*name) );
1026             /* Assume it really was a DOS name */
1027             drive = DRIVE_GetCurrentDrive();
1028         }
1029     }
1030     else drive = DRIVE_GetCurrentDrive();
1031
1032     if (!DRIVE_IsValid(drive))
1033     {
1034         SetLastError( ERROR_INVALID_DRIVE );
1035         return -1;
1036     }
1037     return drive;
1038 }
1039
1040
1041 /***********************************************************************
1042  *           DOSFS_GetFullName
1043  *
1044  * Convert a file name (DOS or mixed DOS/Unix format) to a valid
1045  * Unix name / short DOS name pair.
1046  * Return FALSE if one of the path components does not exist. The last path
1047  * component is only checked if 'check_last' is non-zero.
1048  * The buffers pointed to by 'long_buf' and 'short_buf' must be
1049  * at least MAX_PATHNAME_LEN long.
1050  */
1051 BOOL DOSFS_GetFullName( LPCWSTR name, BOOL check_last, DOS_FULL_NAME *full )
1052 {
1053     BOOL found;
1054     UINT flags;
1055     char *p_l, *root;
1056     LPWSTR p_s;
1057     static const WCHAR driveA_rootW[] = {'A',':','\\',0};
1058     static const WCHAR dos_rootW[] = {'\\',0};
1059
1060     TRACE("%s (last=%d)\n", debugstr_w(name), check_last );
1061
1062     if ((!*name) || (*name=='\n'))
1063     { /* error code for Win98 */
1064         SetLastError(ERROR_BAD_PATHNAME);
1065         return FALSE;
1066     }
1067
1068     if ((full->drive = DOSFS_GetPathDrive( &name )) == -1) return FALSE;
1069     flags = DRIVE_GetFlags( full->drive );
1070
1071     lstrcpynA( full->long_name, DRIVE_GetRoot( full->drive ),
1072                  sizeof(full->long_name) );
1073     if (full->long_name[1]) root = full->long_name + strlen(full->long_name);
1074     else root = full->long_name;  /* root directory */
1075
1076     strcpyW( full->short_name, driveA_rootW );
1077     full->short_name[0] += full->drive;
1078
1079     if ((*name == '\\') || (*name == '/'))  /* Absolute path */
1080     {
1081         while ((*name == '\\') || (*name == '/')) name++;
1082     }
1083     else  /* Relative path */
1084     {
1085         lstrcpynA( root + 1, DRIVE_GetUnixCwd( full->drive ),
1086                      sizeof(full->long_name) - (root - full->long_name) - 1 );
1087         if (root[1]) *root = '/';
1088         lstrcpynW( full->short_name + 3, DRIVE_GetDosCwd( full->drive ),
1089                    sizeof(full->short_name)/sizeof(full->short_name[0]) - 3 );
1090     }
1091
1092     p_l = full->long_name[1] ? full->long_name + strlen(full->long_name)
1093                              : full->long_name;
1094     p_s = full->short_name[3] ? full->short_name + strlenW(full->short_name)
1095                               : full->short_name + 2;
1096     found = TRUE;
1097
1098     while (*name && found)
1099     {
1100         /* Check for '.' and '..' */
1101
1102         if (*name == '.')
1103         {
1104             if (IS_END_OF_NAME(name[1]))
1105             {
1106                 name++;
1107                 while ((*name == '\\') || (*name == '/')) name++;
1108                 continue;
1109             }
1110             else if ((name[1] == '.') && IS_END_OF_NAME(name[2]))
1111             {
1112                 name += 2;
1113                 while ((*name == '\\') || (*name == '/')) name++;
1114                 while ((p_l > root) && (*p_l != '/')) p_l--;
1115                 while ((p_s > full->short_name + 2) && (*p_s != '\\')) p_s--;
1116                 *p_l = *p_s = '\0';  /* Remove trailing separator */
1117                 continue;
1118             }
1119         }
1120
1121         /* Make sure buffers are large enough */
1122
1123         if ((p_s >= full->short_name + sizeof(full->short_name)/sizeof(full->short_name[0]) - 14) ||
1124             (p_l >= full->long_name + sizeof(full->long_name) - 1))
1125         {
1126             SetLastError( ERROR_PATH_NOT_FOUND );
1127             return FALSE;
1128         }
1129
1130         /* Get the long and short name matching the file name */
1131
1132         if ((found = DOSFS_FindUnixName( full, name, p_l + 1,
1133                          sizeof(full->long_name) - (p_l - full->long_name) - 1,
1134                          p_s + 1, !(flags & DRIVE_CASE_SENSITIVE) )))
1135         {
1136             *p_l++ = '/';
1137             p_l   += strlen(p_l);
1138             *p_s++ = '\\';
1139             p_s   += strlenW(p_s);
1140             while (!IS_END_OF_NAME(*name)) name++;
1141         }
1142         else if (!check_last)
1143         {
1144             *p_l++ = '/';
1145             *p_s++ = '\\';
1146             while (!IS_END_OF_NAME(*name) &&
1147                    (p_s < full->short_name + sizeof(full->short_name)/sizeof(full->short_name[0]) - 1) &&
1148                    (p_l < full->long_name + sizeof(full->long_name) - 1))
1149             {
1150                 WCHAR wch;
1151                 *p_s++ = tolowerW(*name);
1152                 /* If the drive is case-sensitive we want to create new */
1153                 /* files in lower-case otherwise we can't reopen them   */
1154                 /* under the same short name. */
1155                 if (flags & DRIVE_CASE_SENSITIVE) wch = tolowerW(*name);
1156                 else wch = *name;
1157                 p_l += WideCharToMultiByte(CP_UNIXCP, 0, &wch, 1, p_l, 2, NULL, NULL);
1158                 name++;
1159             }
1160             /* Ignore trailing dots and spaces */
1161             while(p_l[-1] == '.' || p_l[-1] == ' ') {
1162                 --p_l;
1163                 --p_s;
1164             }
1165             *p_l = '\0';
1166             *p_s = '\0';
1167         }
1168         while ((*name == '\\') || (*name == '/')) name++;
1169     }
1170
1171     if (!found)
1172     {
1173         if (check_last)
1174         {
1175             SetLastError( ERROR_FILE_NOT_FOUND );
1176             return FALSE;
1177         }
1178         if (*name)  /* Not last */
1179         {
1180             SetLastError( ERROR_PATH_NOT_FOUND );
1181             return FALSE;
1182         }
1183     }
1184     if (!full->long_name[0]) strcpy( full->long_name, "/" );
1185     if (!full->short_name[2]) strcpyW( full->short_name + 2, dos_rootW );
1186     TRACE("returning %s = %s\n", full->long_name, debugstr_w(full->short_name) );
1187     return TRUE;
1188 }
1189
1190
1191 /***********************************************************************
1192  *           GetShortPathNameW   (KERNEL32.@)
1193  *
1194  * NOTES
1195  *  observed:
1196  *  longpath=NULL: LastError=ERROR_INVALID_PARAMETER, ret=0
1197  *  longpath="" or invalid: LastError=ERROR_BAD_PATHNAME, ret=0
1198  *
1199  * more observations ( with NT 3.51 (WinDD) ):
1200  * longpath <= 8.3 -> just copy longpath to shortpath
1201  * longpath > 8.3  ->
1202  *             a) file does not exist -> return 0, LastError = ERROR_FILE_NOT_FOUND
1203  *             b) file does exist     -> set the short filename.
1204  * - trailing slashes are reproduced in the short name, even if the
1205  *   file is not a directory
1206  * - the absolute/relative path of the short name is reproduced like found
1207  *   in the long name
1208  * - longpath and shortpath may have the same address
1209  * Peter Ganten, 1999
1210  */
1211 DWORD WINAPI GetShortPathNameW( LPCWSTR longpath, LPWSTR shortpath, DWORD shortlen )
1212 {
1213     DOS_FULL_NAME full_name;
1214     WCHAR tmpshortpath[MAX_PATHNAME_LEN];
1215     const WCHAR *p;
1216     DWORD sp = 0, lp = 0;
1217     int drive;
1218     DWORD tmplen;
1219     UINT flags;
1220     BOOL unixabsolute = *longpath == '/';
1221
1222     TRACE("%s\n", debugstr_w(longpath));
1223
1224     if (!longpath) {
1225       SetLastError(ERROR_INVALID_PARAMETER);
1226       return 0;
1227     }
1228     if (!longpath[0]) {
1229       SetLastError(ERROR_BAD_PATHNAME);
1230       return 0;
1231     }
1232
1233     /* check for drive letter */
1234     if (!unixabsolute && longpath[1] == ':' ) {
1235       tmpshortpath[0] = longpath[0];
1236       tmpshortpath[1] = ':';
1237       sp = 2;
1238     }
1239
1240     if ( ( drive = DOSFS_GetPathDrive ( &longpath )) == -1 ) return 0;
1241     flags = DRIVE_GetFlags ( drive );
1242
1243     if (unixabsolute && drive != DRIVE_GetCurrentDrive()) {
1244       tmpshortpath[0] = drive + 'A';
1245       tmpshortpath[1] = ':';
1246       sp = 2;
1247     }
1248
1249     while ( longpath[lp] ) {
1250
1251       /* check for path delimiters and reproduce them */
1252       if ( longpath[lp] == '\\' || longpath[lp] == '/' ) {
1253         if (!sp || tmpshortpath[sp-1]!= '\\')
1254         {
1255             /* strip double "\\" */
1256             tmpshortpath[sp] = '\\';
1257             sp++;
1258         }
1259         tmpshortpath[sp]=0;/*terminate string*/
1260         lp++;
1261         continue;
1262       }
1263
1264       tmplen = 0;
1265       for(p = longpath + lp; *p && *p != '/' && *p != '\\'; p++)
1266           tmplen++;
1267       lstrcpynW(tmpshortpath + sp, longpath + lp, tmplen + 1);
1268
1269       /* Check, if the current element is a valid dos name */
1270       if ( DOSFS_ValidDOSName ( longpath + lp, !(flags & DRIVE_CASE_SENSITIVE) ) ) {
1271         sp += tmplen;
1272         lp += tmplen;
1273         continue;
1274       }
1275
1276       /* Check if the file exists and use the existing file name */
1277       if ( DOSFS_GetFullName ( tmpshortpath, TRUE, &full_name ) ) {
1278         strcpyW(tmpshortpath + sp, strrchrW(full_name.short_name, '\\') + 1);
1279         sp += strlenW(tmpshortpath + sp);
1280         lp += tmplen;
1281         continue;
1282       }
1283
1284       TRACE("not found!\n" );
1285       SetLastError ( ERROR_FILE_NOT_FOUND );
1286       return 0;
1287     }
1288     tmpshortpath[sp] = 0;
1289
1290     tmplen = strlenW(tmpshortpath) + 1;
1291     if (tmplen <= shortlen)
1292     {
1293         strcpyW(shortpath, tmpshortpath);
1294         TRACE("returning %s\n", debugstr_w(shortpath));
1295         tmplen--; /* length without 0 */
1296     }
1297
1298     return tmplen;
1299 }
1300
1301
1302 /***********************************************************************
1303  *           GetShortPathNameA   (KERNEL32.@)
1304  */
1305 DWORD WINAPI GetShortPathNameA( LPCSTR longpath, LPSTR shortpath, DWORD shortlen )
1306 {
1307     UNICODE_STRING longpathW;
1308     WCHAR shortpathW[MAX_PATH];
1309     DWORD ret, retW;
1310
1311     if (!longpath)
1312     {
1313         SetLastError(ERROR_INVALID_PARAMETER);
1314         return 0;
1315     }
1316
1317     TRACE("%s\n", debugstr_a(longpath));
1318
1319     if (!RtlCreateUnicodeStringFromAsciiz(&longpathW, longpath))
1320     {
1321         SetLastError(ERROR_NOT_ENOUGH_MEMORY);
1322         return 0;
1323     }
1324
1325     retW = GetShortPathNameW(longpathW.Buffer, shortpathW, MAX_PATH);
1326
1327     if (!retW)
1328         ret = 0;
1329     else if (retW > MAX_PATH)
1330     {
1331         SetLastError(ERROR_FILENAME_EXCED_RANGE);
1332         ret = 0;
1333     }
1334     else
1335     {
1336         ret = WideCharToMultiByte(CP_ACP, 0, shortpathW, -1, NULL, 0, NULL, NULL);
1337         if (ret <= shortlen)
1338         {
1339             WideCharToMultiByte(CP_ACP, 0, shortpathW, -1, shortpath, shortlen, NULL, NULL);
1340             ret--; /* length without 0 */
1341         }
1342     }
1343
1344     RtlFreeUnicodeString(&longpathW);
1345     return ret;
1346 }
1347
1348
1349 /***********************************************************************
1350  *           GetLongPathNameW   (KERNEL32.@)
1351  *
1352  * NOTES
1353  *  observed (Win2000):
1354  *  shortpath=NULL: LastError=ERROR_INVALID_PARAMETER, ret=0
1355  *  shortpath="":   LastError=ERROR_PATH_NOT_FOUND, ret=0
1356  */
1357 DWORD WINAPI GetLongPathNameW( LPCWSTR shortpath, LPWSTR longpath, DWORD longlen )
1358 {
1359     DOS_FULL_NAME full_name;
1360     const char *root;
1361     LPWSTR p;
1362     int drive;
1363     DWORD ret, len = 0;
1364
1365     if (!shortpath) {
1366       SetLastError(ERROR_INVALID_PARAMETER);
1367       return 0;
1368     }
1369     if (!shortpath[0]) {
1370       SetLastError(ERROR_PATH_NOT_FOUND);
1371       return 0;
1372     }
1373
1374     TRACE("%s,%p,%ld\n", debugstr_w(shortpath), longpath, longlen);
1375
1376     if(shortpath[0]=='\\' && shortpath[1]=='\\')
1377     {
1378         ERR("UNC pathname %s\n",debugstr_w(shortpath));
1379         lstrcpynW( longpath, full_name.short_name, longlen );
1380         return strlenW(longpath);
1381     }
1382
1383     if (!DOSFS_GetFullName( shortpath, TRUE, &full_name )) return 0;
1384
1385     root = full_name.long_name;
1386     drive = DRIVE_FindDriveRoot(&root);
1387
1388     ret = MultiByteToWideChar(CP_UNIXCP, 0, root, -1, NULL, 0);
1389     ret += 3; /* A:\ */
1390     /* reproduce terminating slash */
1391     if (ret > 4) /* if not drive root */
1392     {
1393         len = strlenW(shortpath);
1394         if (shortpath[len - 1] == '\\' || shortpath[len - 1] == '/')
1395             len = 1;
1396     }
1397     ret += len;
1398     if (ret <= longlen)
1399     {
1400         longpath[0] = 'A' + drive;
1401         longpath[1] = ':';
1402         MultiByteToWideChar(CP_UNIXCP, 0, root, -1, longpath + 2, longlen - 2);
1403         for (p = longpath; *p; p++) if (*p == '/') *p = '\\';
1404         if (len)
1405         {
1406             longpath[ret - 2] = '\\';
1407             longpath[ret - 1] = 0;
1408         }
1409         TRACE("returning %s\n", debugstr_w(longpath));
1410         ret--; /* length without 0 */
1411     }
1412     return ret;
1413 }
1414
1415
1416 /***********************************************************************
1417  *           GetLongPathNameA   (KERNEL32.@)
1418  */
1419 DWORD WINAPI GetLongPathNameA( LPCSTR shortpath, LPSTR longpath, DWORD longlen )
1420 {
1421     UNICODE_STRING shortpathW;
1422     WCHAR longpathW[MAX_PATH];
1423     DWORD ret, retW;
1424
1425     if (!shortpath)
1426     {
1427         SetLastError(ERROR_INVALID_PARAMETER);
1428         return 0;
1429     }
1430
1431     TRACE("%s\n", debugstr_a(shortpath));
1432
1433     if (!RtlCreateUnicodeStringFromAsciiz(&shortpathW, shortpath))
1434     {
1435         SetLastError(ERROR_NOT_ENOUGH_MEMORY);
1436         return 0;
1437     }
1438
1439     retW = GetLongPathNameW(shortpathW.Buffer, longpathW, MAX_PATH);
1440
1441     if (!retW)
1442         ret = 0;
1443     else if (retW > MAX_PATH)
1444     {
1445         SetLastError(ERROR_FILENAME_EXCED_RANGE);
1446         ret = 0;
1447     }
1448     else
1449     {
1450         ret = WideCharToMultiByte(CP_ACP, 0, longpathW, -1, NULL, 0, NULL, NULL);
1451         if (ret <= longlen)
1452         {
1453             WideCharToMultiByte(CP_ACP, 0, longpathW, -1, longpath, longlen, NULL, NULL);
1454             ret--; /* length without 0 */
1455         }
1456     }
1457
1458     RtlFreeUnicodeString(&shortpathW);
1459     return ret;
1460 }
1461
1462
1463 /***********************************************************************
1464  *           DOSFS_DoGetFullPathName
1465  *
1466  * Implementation of GetFullPathNameA/W.
1467  *
1468  * bon@elektron 000331:
1469  * A test for GetFullPathName with many pathological cases
1470  * now gives identical output for Wine and OSR2
1471  */
1472 static DWORD DOSFS_DoGetFullPathName( LPCWSTR name, DWORD len, LPWSTR result )
1473 {
1474     DWORD ret;
1475     DOS_FULL_NAME full_name;
1476     LPWSTR p, q;
1477     char *p_l;
1478     const char * root;
1479     WCHAR drivecur[] = {'C',':','.',0};
1480     WCHAR driveletter=0;
1481     int namelen,drive=0;
1482     static const WCHAR bkslashW[] = {'\\',0};
1483     static const WCHAR dotW[] = {'.',0};
1484     static const WCHAR updir_slashW[] = {'\\','.','.','\\',0};
1485     static const WCHAR curdirW[] = {'\\','.','\\',0};
1486     static const WCHAR updirW[] = {'\\','.','.',0};
1487
1488     if (!name[0])
1489     {
1490         SetLastError(ERROR_BAD_PATHNAME);
1491         return 0;
1492     }
1493
1494     TRACE("passed %s\n", debugstr_w(name));
1495
1496     if (name[1]==':')
1497       /*drive letter given */
1498       {
1499         driveletter = name[0];
1500       }
1501     if ((name[1]==':') && ((name[2]=='\\') || (name[2]=='/')))
1502       /*absolute path given */
1503       {
1504         strncpyW(full_name.short_name, name, MAX_PATHNAME_LEN);
1505         full_name.short_name[MAX_PATHNAME_LEN - 1] = 0; /* ensure 0 termination */
1506         drive = toupperW(name[0]) - 'A';
1507       }
1508     else
1509       {
1510         if (driveletter)
1511           drivecur[0]=driveletter;
1512         else if ((name[0]=='\\') || (name[0]=='/'))
1513           strcpyW(drivecur, bkslashW);
1514         else
1515           strcpyW(drivecur, dotW);
1516
1517         if (!DOSFS_GetFullName( drivecur, FALSE, &full_name ))
1518           {
1519             FIXME("internal: error getting drive/path\n");
1520             return 0;
1521           }
1522         /* find path that drive letter substitutes*/
1523         drive = toupperW(full_name.short_name[0]) - 'A';
1524         root= DRIVE_GetRoot(drive);
1525         if (!root)
1526           {
1527             FIXME("internal: error getting DOS Drive Root\n");
1528             return 0;
1529           }
1530         if (!strcmp(root,"/"))
1531           {
1532             /* we have just the last / and we need it. */
1533             p_l = full_name.long_name;
1534           }
1535         else
1536           {
1537             p_l = full_name.long_name + strlen(root);
1538           }
1539         /* append long name (= unix name) to drive */
1540         MultiByteToWideChar(CP_UNIXCP, 0, p_l, -1, full_name.short_name + 2, MAX_PATHNAME_LEN - 3);
1541         /* append name to treat */
1542         namelen= strlenW(full_name.short_name);
1543         p = (LPWSTR)name;
1544         if (driveletter)
1545           p += 2; /* skip drive name when appending */
1546         if (namelen + 2 + strlenW(p) > MAX_PATHNAME_LEN)
1547           {
1548             FIXME("internal error: buffer too small\n");
1549              return 0;
1550           }
1551         full_name.short_name[namelen++] ='\\';
1552         full_name.short_name[namelen] = 0;
1553         strncpyW(full_name.short_name + namelen, p, MAX_PATHNAME_LEN - namelen);
1554         full_name.short_name[MAX_PATHNAME_LEN - 1] = 0; /* ensure 0 termination */
1555       }
1556     /* reverse all slashes */
1557     for (p=full_name.short_name;
1558          p < full_name.short_name + strlenW(full_name.short_name);
1559          p++)
1560       {
1561         if ( *p == '/' )
1562           *p = '\\';
1563       }
1564      /* Use memmove, as areas overlap */
1565      /* Delete .. */
1566     while ((p = strstrW(full_name.short_name, updir_slashW)))
1567       {
1568         if (p > full_name.short_name+2)
1569           {
1570             *p = 0;
1571             q = strrchrW(full_name.short_name, '\\');
1572             memmove(q+1, p+4, (strlenW(p+4)+1) * sizeof(WCHAR));
1573           }
1574         else
1575           {
1576             memmove(full_name.short_name+3, p+4, (strlenW(p+4)+1) * sizeof(WCHAR));
1577           }
1578       }
1579     if ((full_name.short_name[2]=='.')&&(full_name.short_name[3]=='.'))
1580         {
1581           /* This case istn't treated yet : c:..\test */
1582           memmove(full_name.short_name+2,full_name.short_name+4,
1583                   (strlenW(full_name.short_name+4)+1) * sizeof(WCHAR));
1584         }
1585      /* Delete . */
1586     while ((p = strstrW(full_name.short_name, curdirW)))
1587       {
1588         *(p+1) = 0;
1589         memmove(p+1, p+3, (strlenW(p+3)+1) * sizeof(WCHAR));
1590       }
1591     if (!(DRIVE_GetFlags(drive) & DRIVE_CASE_PRESERVING))
1592         for (p = full_name.short_name; *p; p++) *p = toupperW(*p);
1593     namelen = strlenW(full_name.short_name);
1594     if (!strcmpW(full_name.short_name+namelen-3, updirW))
1595         {
1596           /* one more strange case: "c:\test\test1\.."
1597            return "c:\test" */
1598           *(full_name.short_name+namelen-3)=0;
1599           q = strrchrW(full_name.short_name, '\\');
1600           *q =0;
1601         }
1602     if (full_name.short_name[namelen-1]=='.')
1603         full_name.short_name[(namelen--)-1] =0;
1604     if (!driveletter)
1605       if (full_name.short_name[namelen-1]=='\\')
1606         full_name.short_name[(namelen--)-1] =0;
1607     TRACE("got %s\n", debugstr_w(full_name.short_name));
1608
1609     /* If the lpBuffer buffer is too small, the return value is the
1610     size of the buffer, in characters, required to hold the path
1611     plus the terminating \0 (tested against win95osr2, bon 001118)
1612     . */
1613     ret = strlenW(full_name.short_name);
1614     if (ret >= len )
1615       {
1616         /* don't touch anything when the buffer is not large enough */
1617         SetLastError( ERROR_INSUFFICIENT_BUFFER );
1618         return ret+1;
1619       }
1620     if (result)
1621     {
1622         strncpyW( result, full_name.short_name, len );
1623         result[len - 1] = 0; /* ensure 0 termination */
1624     }
1625
1626     TRACE("returning %s\n", debugstr_w(full_name.short_name) );
1627     return ret;
1628 }
1629
1630
1631 /***********************************************************************
1632  *           GetFullPathNameA   (KERNEL32.@)
1633  * NOTES
1634  *   if the path closed with '\', *lastpart is 0
1635  */
1636 DWORD WINAPI GetFullPathNameA( LPCSTR name, DWORD len, LPSTR buffer,
1637                                  LPSTR *lastpart )
1638 {
1639     UNICODE_STRING nameW;
1640     WCHAR bufferW[MAX_PATH];
1641     DWORD ret, retW;
1642
1643     if (!name)
1644     {
1645         SetLastError(ERROR_INVALID_PARAMETER);
1646         return 0;
1647     }
1648
1649     if (!RtlCreateUnicodeStringFromAsciiz(&nameW, name))
1650     {
1651         SetLastError(ERROR_NOT_ENOUGH_MEMORY);
1652         return 0;
1653     }
1654
1655     retW = GetFullPathNameW( nameW.Buffer, MAX_PATH, bufferW, NULL);
1656
1657     if (!retW)
1658         ret = 0;
1659     else if (retW > MAX_PATH)
1660     {
1661         SetLastError(ERROR_FILENAME_EXCED_RANGE);
1662         ret = 0;
1663     }
1664     else
1665     {
1666         ret = WideCharToMultiByte(CP_ACP, 0, bufferW, -1, NULL, 0, NULL, NULL);
1667         if (ret <= len)
1668         {
1669             WideCharToMultiByte(CP_ACP, 0, bufferW, -1, buffer, len, NULL, NULL);
1670             ret--; /* length without 0 */
1671
1672             if (lastpart)
1673             {
1674                 LPSTR p = buffer + strlen(buffer);
1675
1676                 if (*p != '\\')
1677                 {
1678                     while ((p > buffer + 2) && (*p != '\\')) p--;
1679                     *lastpart = p + 1;
1680                 }
1681                 else *lastpart = NULL;
1682             }
1683         }
1684     }
1685
1686     RtlFreeUnicodeString(&nameW);
1687     return ret;
1688 }
1689
1690
1691 /***********************************************************************
1692  *           GetFullPathNameW   (KERNEL32.@)
1693  */
1694 DWORD WINAPI GetFullPathNameW( LPCWSTR name, DWORD len, LPWSTR buffer,
1695                                  LPWSTR *lastpart )
1696 {
1697     DWORD ret = DOSFS_DoGetFullPathName( name, len, buffer );
1698     if (ret && (ret<=len) && buffer && lastpart)
1699     {
1700         LPWSTR p = buffer + strlenW(buffer);
1701         if (*p != (WCHAR)'\\')
1702         {
1703             while ((p > buffer + 2) && (*p != (WCHAR)'\\')) p--;
1704             *lastpart = p + 1;
1705         }
1706         else *lastpart = NULL;
1707     }
1708     return ret;
1709 }
1710
1711
1712 /***********************************************************************
1713  *           wine_get_unix_file_name (KERNEL32.@) Not a Windows API
1714  *
1715  * Return the full Unix file name for a given path.
1716  * FIXME: convert dos file name to unicode
1717  */
1718 BOOL WINAPI wine_get_unix_file_name( LPCSTR dos, LPSTR buffer, DWORD len )
1719 {
1720     BOOL ret;
1721     DOS_FULL_NAME path;
1722     WCHAR dosW[MAX_PATHNAME_LEN];
1723
1724     MultiByteToWideChar(CP_ACP, 0, dos, -1, dosW, MAX_PATHNAME_LEN);
1725     ret = DOSFS_GetFullName( dosW, FALSE, &path );
1726     if (ret && len)
1727     {
1728         strncpy( buffer, path.long_name, len );
1729         buffer[len - 1] = 0; /* ensure 0 termination */
1730     }
1731     return ret;
1732 }
1733
1734
1735 /***********************************************************************
1736  *           get_show_dir_symlinks_option
1737  */
1738 static BOOL get_show_dir_symlinks_option(void)
1739 {
1740     static const WCHAR WineW[] = {'M','a','c','h','i','n','e','\\',
1741                                   'S','o','f','t','w','a','r','e','\\',
1742                                   'W','i','n','e','\\','W','i','n','e','\\',
1743                                   'C','o','n','f','i','g','\\','W','i','n','e',0};
1744     static const WCHAR ShowDirSymlinksW[] = {'S','h','o','w','D','i','r','S','y','m','l','i','n','k','s',0};
1745
1746     char tmp[80];
1747     HKEY hkey;
1748     DWORD dummy;
1749     OBJECT_ATTRIBUTES attr;
1750     UNICODE_STRING nameW;
1751     BOOL ret = FALSE;
1752
1753     attr.Length = sizeof(attr);
1754     attr.RootDirectory = 0;
1755     attr.ObjectName = &nameW;
1756     attr.Attributes = 0;
1757     attr.SecurityDescriptor = NULL;
1758     attr.SecurityQualityOfService = NULL;
1759     RtlInitUnicodeString( &nameW, WineW );
1760
1761     if (!NtOpenKey( &hkey, KEY_ALL_ACCESS, &attr ))
1762     {
1763         RtlInitUnicodeString( &nameW, ShowDirSymlinksW );
1764         if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
1765         {
1766             WCHAR *str = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
1767             ret = IS_OPTION_TRUE( str[0] );
1768         }
1769         NtClose( hkey );
1770     }
1771     return ret;
1772 }
1773
1774
1775 /***********************************************************************
1776  *           DOSFS_FindNextEx
1777  */
1778 static int DOSFS_FindNextEx( FIND_FIRST_INFO *info, WIN32_FIND_DATAW *entry )
1779 {
1780     DWORD attr = info->attr | FA_UNUSED | FA_ARCHIVE | FA_RDONLY;
1781     UINT flags = DRIVE_GetFlags( info->drive );
1782     char *p, buffer[MAX_PATHNAME_LEN];
1783     const char *drive_path;
1784     int drive_root;
1785     LPCWSTR long_name, short_name;
1786     BY_HANDLE_FILE_INFORMATION fileinfo;
1787     WCHAR dos_name[13];
1788     BOOL is_symlink;
1789
1790     if ((info->attr & ~(FA_UNUSED | FA_ARCHIVE | FA_RDONLY)) == FA_LABEL)
1791     {
1792         if (info->cur_pos) return 0;
1793         entry->dwFileAttributes  = FILE_ATTRIBUTE_LABEL;
1794         RtlSecondsSince1970ToTime( (time_t)0, (LARGE_INTEGER *)&entry->ftCreationTime );
1795         RtlSecondsSince1970ToTime( (time_t)0, (LARGE_INTEGER *)&entry->ftLastAccessTime );
1796         RtlSecondsSince1970ToTime( (time_t)0, (LARGE_INTEGER *)&entry->ftLastWriteTime );
1797         entry->nFileSizeHigh     = 0;
1798         entry->nFileSizeLow      = 0;
1799         entry->dwReserved0       = 0;
1800         entry->dwReserved1       = 0;
1801         DOSFS_ToDosDTAFormat( DRIVE_GetLabel( info->drive ), entry->cFileName );
1802         strcpyW( entry->cAlternateFileName, entry->cFileName );
1803         info->cur_pos++;
1804         TRACE("returning %s (%s) as label\n",
1805                debugstr_w(entry->cFileName), debugstr_w(entry->cAlternateFileName));
1806         return 1;
1807     }
1808
1809     drive_path = info->path + strlen(DRIVE_GetRoot( info->drive ));
1810     while ((*drive_path == '/') || (*drive_path == '\\')) drive_path++;
1811     drive_root = !*drive_path;
1812
1813     lstrcpynA( buffer, info->path, sizeof(buffer) - 1 );
1814     strcat( buffer, "/" );
1815     p = buffer + strlen(buffer);
1816
1817     while (DOSFS_ReadDir( info->u.dos_dir, &long_name, &short_name ))
1818     {
1819         info->cur_pos++;
1820
1821         /* Don't return '.' and '..' in the root of the drive */
1822         if (drive_root && (long_name[0] == '.') &&
1823             (!long_name[1] || ((long_name[1] == '.') && !long_name[2])))
1824             continue;
1825
1826         /* Check the long mask */
1827
1828         if (info->long_mask && *info->long_mask)
1829         {
1830             if (!DOSFS_MatchLong( info->long_mask, long_name,
1831                                   flags & DRIVE_CASE_SENSITIVE )) continue;
1832         }
1833
1834         /* Check the short mask */
1835
1836         if (info->short_mask)
1837         {
1838             if (!short_name)
1839             {
1840                 DOSFS_Hash( long_name, dos_name, TRUE,
1841                             !(flags & DRIVE_CASE_SENSITIVE) );
1842                 short_name = dos_name;
1843             }
1844             if (!DOSFS_MatchShort( info->short_mask, short_name )) continue;
1845         }
1846
1847         /* Check the file attributes */
1848         WideCharToMultiByte(CP_UNIXCP, 0, long_name, -1,
1849                             p, sizeof(buffer) - (int)(p - buffer), NULL, NULL);
1850         if (!FILE_Stat( buffer, &fileinfo, &is_symlink ))
1851         {
1852             WARN("can't stat %s\n", buffer);
1853             continue;
1854         }
1855         if (is_symlink && (fileinfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
1856         {
1857             static int show_dir_symlinks = -1;
1858             if (show_dir_symlinks == -1)
1859                 show_dir_symlinks = get_show_dir_symlinks_option();
1860             if (!show_dir_symlinks) continue;
1861         }
1862
1863         if (fileinfo.dwFileAttributes & ~attr) continue;
1864
1865         /* We now have a matching entry; fill the result and return */
1866
1867         entry->dwFileAttributes = fileinfo.dwFileAttributes;
1868         entry->ftCreationTime   = fileinfo.ftCreationTime;
1869         entry->ftLastAccessTime = fileinfo.ftLastAccessTime;
1870         entry->ftLastWriteTime  = fileinfo.ftLastWriteTime;
1871         entry->nFileSizeHigh    = fileinfo.nFileSizeHigh;
1872         entry->nFileSizeLow     = fileinfo.nFileSizeLow;
1873
1874         if (short_name)
1875             DOSFS_ToDosDTAFormat( short_name, entry->cAlternateFileName );
1876         else
1877             DOSFS_Hash( long_name, entry->cAlternateFileName, FALSE,
1878                         !(flags & DRIVE_CASE_SENSITIVE) );
1879
1880         lstrcpynW( entry->cFileName, long_name, sizeof(entry->cFileName)/sizeof(entry->cFileName[0]) );
1881         if (!(flags & DRIVE_CASE_PRESERVING)) strlwrW( entry->cFileName );
1882         TRACE("returning %s (%s) %02lx %ld\n",
1883               debugstr_w(entry->cFileName), debugstr_w(entry->cAlternateFileName),
1884               entry->dwFileAttributes, entry->nFileSizeLow );
1885         return 1;
1886     }
1887     return 0;  /* End of directory */
1888 }
1889
1890 /***********************************************************************
1891  *           DOSFS_FindNext
1892  *
1893  * Find the next matching file. Return the number of entries read to find
1894  * the matching one, or 0 if no more entries.
1895  * 'short_mask' is the 8.3 mask (in FCB format), 'long_mask' is the long
1896  * file name mask. Either or both can be NULL.
1897  *
1898  * NOTE: This is supposed to be only called by the int21 emulation
1899  *       routines, and so assumes that the Win16Mutex is held to
1900  *       protect the static directory cache.
1901  */
1902 int DOSFS_FindNext( const char *path, const char *short_mask,
1903                     const char *long_mask, int drive, BYTE attr,
1904                     int skip, WIN32_FIND_DATAA *entry )
1905 {
1906     static FIND_FIRST_INFO info;
1907     LPCWSTR short_name, long_name;
1908     int count;
1909     UNICODE_STRING short_maskW, long_maskW;
1910     WIN32_FIND_DATAW entryW;
1911
1912     TRACE("(%s, %s, %s, %x, %x, %x, %p)\n", debugstr_a(path),
1913           debugstr_a(short_mask), debugstr_a(long_mask), drive, attr, skip,
1914           entry);
1915
1916     RtlCreateUnicodeStringFromAsciiz(&short_maskW, short_mask);
1917     RtlCreateUnicodeStringFromAsciiz(&long_maskW, long_mask);
1918
1919     /* Check the cached directory */
1920     if (!(info.u.dos_dir && info.path == path && !strcmpW(info.short_mask, short_maskW.Buffer)
1921                    && !strcmpW(info.long_mask, long_maskW.Buffer) && info.drive == drive
1922                    && info.attr == attr && info.cur_pos <= skip))
1923     {
1924         /* Not in the cache, open it anew */
1925         if (info.u.dos_dir) DOSFS_CloseDir( info.u.dos_dir );
1926
1927         info.path = (LPSTR)path;
1928         RtlFreeHeap(GetProcessHeap(), 0, info.long_mask);
1929         RtlFreeHeap(GetProcessHeap(), 0, info.short_mask);
1930         info.long_mask = long_maskW.Buffer;
1931         info.short_mask = short_maskW.Buffer;
1932         info.attr = attr;
1933         info.drive = drive;
1934         info.cur_pos = 0;
1935         info.u.dos_dir = DOSFS_OpenDir( info.path );
1936     }
1937     else
1938     {
1939         RtlFreeUnicodeString(&short_maskW);
1940         RtlFreeUnicodeString(&long_maskW);
1941     }
1942
1943     /* Skip to desired position */
1944     while (info.cur_pos < skip)
1945         if (info.u.dos_dir && DOSFS_ReadDir( info.u.dos_dir, &long_name, &short_name ))
1946             info.cur_pos++;
1947         else
1948             break;
1949
1950     if (info.u.dos_dir && info.cur_pos == skip && DOSFS_FindNextEx( &info, &entryW ))
1951     {
1952         WideCharToMultiByte(CP_ACP, 0, entryW.cFileName, -1,
1953                             entry->cFileName, sizeof(entry->cFileName), NULL, NULL);
1954         WideCharToMultiByte(CP_ACP, 0, entryW.cAlternateFileName, -1,
1955                             entry->cAlternateFileName, sizeof(entry->cAlternateFileName), NULL, NULL);
1956         count = info.cur_pos - skip;
1957
1958         entry->dwFileAttributes = entryW.dwFileAttributes;
1959         entry->nFileSizeHigh    = entryW.nFileSizeHigh;
1960         entry->nFileSizeLow     = entryW.nFileSizeLow;
1961         entry->ftCreationTime   = entryW.ftCreationTime;
1962         entry->ftLastAccessTime = entryW.ftLastAccessTime;
1963         entry->ftLastWriteTime  = entryW.ftLastWriteTime;
1964
1965     }
1966     else
1967         count = 0;
1968
1969     if (!count)
1970     {
1971         if (info.u.dos_dir) DOSFS_CloseDir( info.u.dos_dir );
1972         memset( &info, '\0', sizeof(info) );
1973     }
1974
1975     return count;
1976 }
1977
1978 /*************************************************************************
1979  *           FindFirstFileExW  (KERNEL32.@)
1980  */
1981 HANDLE WINAPI FindFirstFileExW(
1982         LPCWSTR lpFileName,
1983         FINDEX_INFO_LEVELS fInfoLevelId,
1984         LPVOID lpFindFileData,
1985         FINDEX_SEARCH_OPS fSearchOp,
1986         LPVOID lpSearchFilter,
1987         DWORD dwAdditionalFlags)
1988 {
1989     FIND_FIRST_INFO *info;
1990
1991     if (!lpFileName)
1992     {
1993         SetLastError(ERROR_PATH_NOT_FOUND);
1994         return INVALID_HANDLE_VALUE;
1995     }
1996
1997     if ((fSearchOp != FindExSearchNameMatch) || (dwAdditionalFlags != 0))
1998     {
1999         FIXME("options not implemented 0x%08x 0x%08lx\n", fSearchOp, dwAdditionalFlags );
2000         return INVALID_HANDLE_VALUE;
2001     }
2002
2003     switch(fInfoLevelId)
2004     {
2005       case FindExInfoStandard:
2006         {
2007           WIN32_FIND_DATAW * data = (WIN32_FIND_DATAW *) lpFindFileData;
2008           char *p;
2009           INT long_mask_len;
2010
2011           data->dwReserved0 = data->dwReserved1 = 0x0;
2012           if (lpFileName[0] == '\\' && lpFileName[1] == '\\')
2013           {
2014               ERR("UNC path name\n");
2015               if (!(info = HeapAlloc( GetProcessHeap(), 0, sizeof(FIND_FIRST_INFO)))) break;
2016               info->u.smb_dir = SMB_FindFirst(lpFileName);
2017               if(!info->u.smb_dir)
2018               {
2019                  HeapFree(GetProcessHeap(), 0, info);
2020                  break;
2021               }
2022               info->drive = -1;
2023               RtlInitializeCriticalSection( &info->cs );
2024           }
2025           else
2026           {
2027             DOS_FULL_NAME full_name;
2028
2029             if (lpFileName[0] && lpFileName[1] == ':')
2030             {
2031                 /* don't allow root directories */
2032                 if (!lpFileName[2] ||
2033                     ((lpFileName[2] == '/' || lpFileName[2] == '\\') && !lpFileName[3]))
2034                 {
2035                     SetLastError(ERROR_FILE_NOT_FOUND);
2036                     return INVALID_HANDLE_VALUE;
2037                 }
2038             }
2039             if (!DOSFS_GetFullName( lpFileName, FALSE, &full_name )) break;
2040             if (!(info = HeapAlloc( GetProcessHeap(), 0, sizeof(FIND_FIRST_INFO)))) break;
2041             RtlInitializeCriticalSection( &info->cs );
2042             info->path = HeapAlloc( GetProcessHeap(), 0, strlen(full_name.long_name)+1 );
2043             strcpy( info->path, full_name.long_name );
2044
2045             p = strrchr( info->path, '/' );
2046             *p++ = '\0';
2047             long_mask_len = MultiByteToWideChar(CP_UNIXCP, 0, p, -1, NULL, 0);
2048             info->long_mask = HeapAlloc( GetProcessHeap(), 0, long_mask_len * sizeof(WCHAR) );
2049             MultiByteToWideChar(CP_UNIXCP, 0, p, -1, info->long_mask, long_mask_len);
2050
2051             info->short_mask = NULL;
2052             info->attr = 0xff;
2053             info->drive = full_name.drive;
2054             info->cur_pos = 0;
2055
2056             info->u.dos_dir = DOSFS_OpenDir( info->path );
2057           }
2058           if (!FindNextFileW( (HANDLE) info, data ))
2059           {
2060               FindClose( (HANDLE) info );
2061               SetLastError( ERROR_FILE_NOT_FOUND );
2062               break;
2063           }
2064           return (HANDLE) info;
2065         }
2066         break;
2067       default:
2068         FIXME("fInfoLevelId 0x%08x not implemented\n", fInfoLevelId );
2069     }
2070     return INVALID_HANDLE_VALUE;
2071 }
2072
2073 /*************************************************************************
2074  *           FindFirstFileA   (KERNEL32.@)
2075  */
2076 HANDLE WINAPI FindFirstFileA(
2077         LPCSTR lpFileName,
2078         WIN32_FIND_DATAA *lpFindData )
2079 {
2080     return FindFirstFileExA(lpFileName, FindExInfoStandard, lpFindData,
2081                             FindExSearchNameMatch, NULL, 0);
2082 }
2083
2084 /*************************************************************************
2085  *           FindFirstFileExA   (KERNEL32.@)
2086  */
2087 HANDLE WINAPI FindFirstFileExA(
2088         LPCSTR lpFileName,
2089         FINDEX_INFO_LEVELS fInfoLevelId,
2090         LPVOID lpFindFileData,
2091         FINDEX_SEARCH_OPS fSearchOp,
2092         LPVOID lpSearchFilter,
2093         DWORD dwAdditionalFlags)
2094 {
2095     HANDLE handle;
2096     WIN32_FIND_DATAA *dataA;
2097     WIN32_FIND_DATAW dataW;
2098     UNICODE_STRING pathW;
2099
2100     if (!lpFileName)
2101     {
2102         SetLastError(ERROR_PATH_NOT_FOUND);
2103         return INVALID_HANDLE_VALUE;
2104     }
2105
2106     if (!RtlCreateUnicodeStringFromAsciiz(&pathW, lpFileName))
2107     {
2108         SetLastError(ERROR_NOT_ENOUGH_MEMORY);
2109         return INVALID_HANDLE_VALUE;
2110     }
2111
2112     handle = FindFirstFileExW(pathW.Buffer, fInfoLevelId, &dataW, fSearchOp, lpSearchFilter, dwAdditionalFlags);
2113     RtlFreeUnicodeString(&pathW);
2114     if (handle == INVALID_HANDLE_VALUE) return handle;
2115
2116     dataA = (WIN32_FIND_DATAA *) lpFindFileData;
2117     dataA->dwFileAttributes = dataW.dwFileAttributes;
2118     dataA->ftCreationTime   = dataW.ftCreationTime;
2119     dataA->ftLastAccessTime = dataW.ftLastAccessTime;
2120     dataA->ftLastWriteTime  = dataW.ftLastWriteTime;
2121     dataA->nFileSizeHigh    = dataW.nFileSizeHigh;
2122     dataA->nFileSizeLow     = dataW.nFileSizeLow;
2123     WideCharToMultiByte( CP_ACP, 0, dataW.cFileName, -1,
2124                          dataA->cFileName, sizeof(dataA->cFileName), NULL, NULL );
2125     WideCharToMultiByte( CP_ACP, 0, dataW.cAlternateFileName, -1,
2126                          dataA->cAlternateFileName, sizeof(dataA->cAlternateFileName), NULL, NULL );
2127     return handle;
2128 }
2129
2130 /*************************************************************************
2131  *           FindFirstFileW   (KERNEL32.@)
2132  */
2133 HANDLE WINAPI FindFirstFileW( LPCWSTR lpFileName, WIN32_FIND_DATAW *lpFindData )
2134 {
2135     return FindFirstFileExW(lpFileName, FindExInfoStandard, lpFindData,
2136                             FindExSearchNameMatch, NULL, 0);
2137 }
2138
2139 /*************************************************************************
2140  *           FindNextFileW   (KERNEL32.@)
2141  */
2142 BOOL WINAPI FindNextFileW( HANDLE handle, WIN32_FIND_DATAW *data )
2143 {
2144     FIND_FIRST_INFO *info;
2145     BOOL ret = FALSE;
2146     DWORD gle = ERROR_NO_MORE_FILES;
2147
2148     if (handle == INVALID_HANDLE_VALUE)
2149     {
2150         SetLastError( ERROR_INVALID_HANDLE );
2151         return ret;
2152     }
2153     info = (FIND_FIRST_INFO*) handle;
2154     RtlEnterCriticalSection( &info->cs );
2155     if (info->drive == -1)
2156     {
2157         ret = SMB_FindNext( info->u.smb_dir, data );
2158         if(!ret)
2159         {
2160             SMB_CloseDir( info->u.smb_dir );
2161             HeapFree( GetProcessHeap(), 0, info->path );
2162         }
2163         goto done;
2164     }
2165     else if (!info->path || !info->u.dos_dir)
2166     {
2167         goto done;
2168     }
2169     else if (!DOSFS_FindNextEx( info, data ))
2170     {
2171         DOSFS_CloseDir( info->u.dos_dir ); info->u.dos_dir = NULL;
2172         HeapFree( GetProcessHeap(), 0, info->path );
2173         info->path = NULL;
2174         HeapFree( GetProcessHeap(), 0, info->long_mask );
2175         info->long_mask = NULL;
2176         goto done;
2177     }
2178     ret = TRUE;
2179 done:
2180     RtlLeaveCriticalSection( &info->cs );
2181     if( !ret ) SetLastError( gle );
2182     return ret;
2183 }
2184
2185
2186 /*************************************************************************
2187  *           FindNextFileA   (KERNEL32.@)
2188  */
2189 BOOL WINAPI FindNextFileA( HANDLE handle, WIN32_FIND_DATAA *data )
2190 {
2191     WIN32_FIND_DATAW dataW;
2192     if (!FindNextFileW( handle, &dataW )) return FALSE;
2193     data->dwFileAttributes = dataW.dwFileAttributes;
2194     data->ftCreationTime   = dataW.ftCreationTime;
2195     data->ftLastAccessTime = dataW.ftLastAccessTime;
2196     data->ftLastWriteTime  = dataW.ftLastWriteTime;
2197     data->nFileSizeHigh    = dataW.nFileSizeHigh;
2198     data->nFileSizeLow     = dataW.nFileSizeLow;
2199     WideCharToMultiByte( CP_ACP, 0, dataW.cFileName, -1,
2200                          data->cFileName, sizeof(data->cFileName), NULL, NULL );
2201     WideCharToMultiByte( CP_ACP, 0, dataW.cAlternateFileName, -1,
2202                          data->cAlternateFileName,
2203                          sizeof(data->cAlternateFileName), NULL, NULL );
2204     return TRUE;
2205 }
2206
2207 /*************************************************************************
2208  *           FindClose   (KERNEL32.@)
2209  */
2210 BOOL WINAPI FindClose( HANDLE handle )
2211 {
2212     FIND_FIRST_INFO *info = (FIND_FIRST_INFO*) handle;
2213
2214     if (handle == INVALID_HANDLE_VALUE) goto error;
2215
2216     __TRY
2217     {
2218         RtlEnterCriticalSection( &info->cs );
2219         if (info)
2220         {
2221             if (info->u.dos_dir) DOSFS_CloseDir( info->u.dos_dir );
2222             if (info->path) HeapFree( GetProcessHeap(), 0, info->path );
2223             if (info->long_mask) HeapFree( GetProcessHeap(), 0, info->long_mask );
2224         }
2225     }
2226     __EXCEPT(page_fault)
2227     {
2228         WARN("Illegal handle %p\n", handle);
2229         SetLastError( ERROR_INVALID_HANDLE );
2230         return FALSE;
2231     }
2232     __ENDTRY
2233     if (!info) goto error;
2234     RtlLeaveCriticalSection( &info->cs );
2235     RtlDeleteCriticalSection( &info->cs );
2236     HeapFree(GetProcessHeap(), 0, info);
2237     return TRUE;
2238
2239  error:
2240     SetLastError( ERROR_INVALID_HANDLE );
2241     return FALSE;
2242 }
2243
2244 /***********************************************************************
2245  *           MulDiv   (KERNEL32.@)
2246  * RETURNS
2247  *      Result of multiplication and division
2248  *      -1: Overflow occurred or Divisor was 0
2249  */
2250 INT WINAPI MulDiv(
2251              INT nMultiplicand,
2252              INT nMultiplier,
2253              INT nDivisor)
2254 {
2255 #if SIZEOF_LONG_LONG >= 8
2256     long long ret;
2257
2258     if (!nDivisor) return -1;
2259
2260     /* We want to deal with a positive divisor to simplify the logic. */
2261     if (nDivisor < 0)
2262     {
2263       nMultiplicand = - nMultiplicand;
2264       nDivisor = -nDivisor;
2265     }
2266
2267     /* If the result is positive, we "add" to round. else, we subtract to round. */
2268     if ( ( (nMultiplicand <  0) && (nMultiplier <  0) ) ||
2269          ( (nMultiplicand >= 0) && (nMultiplier >= 0) ) )
2270       ret = (((long long)nMultiplicand * nMultiplier) + (nDivisor/2)) / nDivisor;
2271     else
2272       ret = (((long long)nMultiplicand * nMultiplier) - (nDivisor/2)) / nDivisor;
2273
2274     if ((ret > 2147483647) || (ret < -2147483647)) return -1;
2275     return ret;
2276 #else
2277     if (!nDivisor) return -1;
2278
2279     /* We want to deal with a positive divisor to simplify the logic. */
2280     if (nDivisor < 0)
2281     {
2282       nMultiplicand = - nMultiplicand;
2283       nDivisor = -nDivisor;
2284     }
2285
2286     /* If the result is positive, we "add" to round. else, we subtract to round. */
2287     if ( ( (nMultiplicand <  0) && (nMultiplier <  0) ) ||
2288          ( (nMultiplicand >= 0) && (nMultiplier >= 0) ) )
2289       return ((nMultiplicand * nMultiplier) + (nDivisor/2)) / nDivisor;
2290
2291     return ((nMultiplicand * nMultiplier) - (nDivisor/2)) / nDivisor;
2292
2293 #endif
2294 }
2295
2296
2297 /***********************************************************************
2298  *           DosDateTimeToFileTime   (KERNEL32.@)
2299  */
2300 BOOL WINAPI DosDateTimeToFileTime( WORD fatdate, WORD fattime, LPFILETIME ft)
2301 {
2302     struct tm newtm;
2303 #ifndef HAVE_TIMEGM
2304     struct tm *gtm;
2305     time_t time1, time2;
2306 #endif
2307
2308     newtm.tm_sec  = (fattime & 0x1f) * 2;
2309     newtm.tm_min  = (fattime >> 5) & 0x3f;
2310     newtm.tm_hour = (fattime >> 11);
2311     newtm.tm_mday = (fatdate & 0x1f);
2312     newtm.tm_mon  = ((fatdate >> 5) & 0x0f) - 1;
2313     newtm.tm_year = (fatdate >> 9) + 80;
2314 #ifdef HAVE_TIMEGM
2315     RtlSecondsSince1970ToTime( timegm(&newtm), (LARGE_INTEGER *)ft );
2316 #else
2317     time1 = mktime(&newtm);
2318     gtm = gmtime(&time1);
2319     time2 = mktime(gtm);
2320     RtlSecondsSince1970ToTime( 2*time1-time2, (LARGE_INTEGER *)ft );
2321 #endif
2322     return TRUE;
2323 }
2324
2325
2326 /***********************************************************************
2327  *           FileTimeToDosDateTime   (KERNEL32.@)
2328  */
2329 BOOL WINAPI FileTimeToDosDateTime( const FILETIME *ft, LPWORD fatdate,
2330                                      LPWORD fattime )
2331 {
2332     LARGE_INTEGER       li;
2333     ULONG               t;
2334     time_t              unixtime;
2335     struct tm*          tm;
2336
2337     li.s.LowPart = ft->dwLowDateTime;
2338     li.s.HighPart = ft->dwHighDateTime;
2339     RtlTimeToSecondsSince1970( &li, &t );
2340     unixtime = t;
2341     tm = gmtime( &unixtime );
2342     if (fattime)
2343         *fattime = (tm->tm_hour << 11) + (tm->tm_min << 5) + (tm->tm_sec / 2);
2344     if (fatdate)
2345         *fatdate = ((tm->tm_year - 80) << 9) + ((tm->tm_mon + 1) << 5)
2346                    + tm->tm_mday;
2347     return TRUE;
2348 }
2349
2350
2351 /***********************************************************************
2352  *           QueryDosDeviceA   (KERNEL32.@)
2353  *
2354  * returns array of strings terminated by \0, terminated by \0
2355  */
2356 DWORD WINAPI QueryDosDeviceA(LPCSTR devname,LPSTR target,DWORD bufsize)
2357 {
2358     DWORD ret = 0, retW;
2359     LPWSTR targetW = (LPWSTR)HeapAlloc(GetProcessHeap(),0,
2360                                      bufsize * sizeof(WCHAR));
2361     UNICODE_STRING devnameW;
2362
2363     if(devname) RtlCreateUnicodeStringFromAsciiz(&devnameW, devname);
2364     else devnameW.Buffer = NULL;
2365
2366     retW = QueryDosDeviceW(devnameW.Buffer, targetW, bufsize);
2367
2368     ret = WideCharToMultiByte(CP_ACP, 0, targetW, retW, target,
2369                                         bufsize, NULL, NULL);
2370
2371     RtlFreeUnicodeString(&devnameW);
2372     if (targetW) HeapFree(GetProcessHeap(),0,targetW);
2373     return ret;
2374 }
2375
2376
2377 /***********************************************************************
2378  *           QueryDosDeviceW   (KERNEL32.@)
2379  *
2380  * returns array of strings terminated by \0, terminated by \0
2381  *
2382  * FIXME
2383  *      - Win9x returns for all calls ERROR_INVALID_PARAMETER 
2384  *      - the returned devices for devname == NULL is far from complete
2385  *      - its not checked that the returned device exist
2386  */
2387 DWORD WINAPI QueryDosDeviceW(LPCWSTR devname,LPWSTR target,DWORD bufsize)
2388 {
2389     const WCHAR *pDev, *pName, *pNum = NULL;
2390     int    numsiz=0;
2391     DWORD  ret;
2392
2393     TRACE("(%s,...)\n", debugstr_w(devname));
2394     if (!devname) {
2395         /* return known MSDOS devices */
2396         DWORD ret = 0;
2397         int i;
2398         static const WCHAR devices[][5] = {{'A','U','X',0},
2399                                            {'C','O','M','1',0},
2400                                            {'C','O','M','2',0},
2401                                            {'L','P','T','1',0},
2402                                            {'N','U','L',0,}};
2403         for(i=0; (i< (sizeof(devices)/sizeof(devices[0]))); i++) {
2404             DWORD len = strlenW(devices[i]);
2405             if(target && (bufsize >= ret + len + 2)) {
2406                 strcpyW(target+ret, devices[i]);
2407                 ret += len + 1;
2408             } else {
2409                 /* in this case WinXP returns 0 */
2410                 FIXME("function return is wrong for WinXP!\n");
2411                 SetLastError(ERROR_INSUFFICIENT_BUFFER);
2412                 break;
2413             }
2414         }
2415         /* append drives here */
2416         if(target && bufsize > 0) target[ret++] = 0;
2417         FIXME("Returned list is not complete\n");
2418         return ret;
2419     }
2420     /* In theory all that are possible and have been defined.
2421      * Now just those below, since mirc uses it to check for special files.
2422      *
2423      * (It is more complex, and supports netmounted stuff, and \\.\ stuff,
2424      *  but currently we just ignore that.)
2425      */
2426     if (!strcmpiW(devname, auxW)) {
2427         pDev   = dosW;
2428         pName  = comW;
2429         numsiz = 1;
2430         pNum   = oneW;
2431     } else if (!strcmpiW(devname, nulW)) {
2432         pDev  = devW;
2433         pName = nullW;
2434     } else if (!strncmpiW(devname, comW, strlenW(comW))) {
2435         pDev  = devW;
2436         pName = serW;
2437         pNum  = devname + strlenW(comW);
2438         for(numsiz=0; isdigitW(*(pNum+numsiz)); numsiz++);
2439         if(*(pNum + numsiz)) {
2440             SetLastError(ERROR_FILE_NOT_FOUND);
2441             return 0;
2442         }
2443     } else if (!strncmpiW(devname, lptW, strlenW(lptW))) {
2444         pDev  = devW;
2445         pName = parW;
2446         pNum  = devname + strlenW(lptW);
2447         for(numsiz=0; isdigitW(*(pNum+numsiz)); numsiz++);
2448         if(*(pNum + numsiz)) {
2449             SetLastError(ERROR_FILE_NOT_FOUND);
2450         return 0;
2451     }
2452     } else {
2453         /* This might be a DOS device we do not handle yet ... */
2454         FIXME("(%s) not detected as DOS device!\n",debugstr_w(devname));
2455
2456         /* Win9x set the error ERROR_INVALID_PARAMETER */
2457         SetLastError(ERROR_FILE_NOT_FOUND);
2458         return 0;
2459 }
2460     FIXME("device %s may not exist on this computer\n", debugstr_w(devname));
2461
2462     ret = strlenW(pDev) + strlenW(pName) + numsiz + 2;
2463     if (ret > bufsize) ret = 0;
2464     if (target && ret) {
2465         strcpyW(target,pDev);
2466         strcatW(target,pName);
2467         if (pNum) strcatW(target,pNum);
2468         target[ret-1] = 0;
2469     }
2470     return ret;
2471 }
2472
2473
2474 /***********************************************************************
2475  *           DefineDosDeviceA       (KERNEL32.@)
2476  */
2477 BOOL WINAPI DefineDosDeviceA(DWORD flags,LPCSTR devname,LPCSTR targetpath) {
2478         FIXME("(0x%08lx,%s,%s),stub!\n",flags,devname,targetpath);
2479         SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
2480         return FALSE;
2481 }