Reimplemented FindFirstFile/FindNextFile on top of
[wine] / files / dos_fs.c
1 /*
2  * DOS file system functions
3  *
4  * Copyright 1993 Erik Bos
5  * Copyright 1996 Alexandre Julliard
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  */
21
22 #include "config.h"
23 #include "wine/port.h"
24
25 #include <sys/types.h>
26 #include <ctype.h>
27 #include <dirent.h>
28 #include <errno.h>
29 #ifdef HAVE_SYS_ERRNO_H
30 #include <sys/errno.h>
31 #endif
32 #include <fcntl.h>
33 #include <stdarg.h>
34 #include <string.h>
35 #include <stdlib.h>
36 #include <sys/stat.h>
37 #ifdef HAVE_SYS_IOCTL_H
38 #include <sys/ioctl.h>
39 #endif
40 #ifdef HAVE_LINUX_IOCTL_H
41 #include <linux/ioctl.h>
42 #endif
43 #include <time.h>
44 #ifdef HAVE_UNISTD_H
45 # include <unistd.h>
46 #endif
47
48 #define NONAMELESSUNION
49 #define NONAMELESSSTRUCT
50 #include "ntstatus.h"
51 #include "windef.h"
52 #include "winbase.h"
53 #include "winerror.h"
54 #include "wingdi.h"
55
56 #include "wine/unicode.h"
57 #include "wine/winbase16.h"
58 #include "file.h"
59 #include "winreg.h"
60 #include "winternl.h"
61 #include "wine/server.h"
62 #include "wine/exception.h"
63 #include "excpt.h"
64
65 #include "smb.h"
66
67 #include "wine/debug.h"
68
69 WINE_DEFAULT_DEBUG_CHANNEL(dosfs);
70 WINE_DECLARE_DEBUG_CHANNEL(file);
71
72 /* Define the VFAT ioctl to get both short and long file names */
73 /* FIXME: is it possible to get this to work on other systems? */
74 #ifdef linux
75 /* We want the real kernel dirent structure, not the libc one */
76 typedef struct
77 {
78     long d_ino;
79     long d_off;
80     unsigned short d_reclen;
81     char d_name[256];
82 } KERNEL_DIRENT;
83
84 #define VFAT_IOCTL_READDIR_BOTH  _IOR('r', 1, KERNEL_DIRENT [2] )
85
86 /* To avoid blocking on non-directories in DOSFS_OpenDir_VFAT*/
87 #ifndef O_DIRECTORY
88 # define O_DIRECTORY    0200000 /* must be directory */
89 #endif
90
91 #else   /* linux */
92 #undef VFAT_IOCTL_READDIR_BOTH  /* just in case... */
93 #endif  /* linux */
94
95 /* Chars we don't want to see in DOS file names */
96 #define INVALID_DOS_CHARS  "*?<>|\"+=,;[] \345"
97
98 /* DOS device descriptor */
99 typedef struct
100 {
101     const WCHAR name[5];
102 } DOS_DEVICE;
103
104 static const DOS_DEVICE DOSFS_Devices[] =
105 /* name, device flags (see Int 21/AX=0x4400) */
106 {
107     { {'C','O','N',0} },
108     { {'P','R','N',0} },
109     { {'N','U','L',0} },
110     { {'A','U','X',0} },
111     { {'L','P','T','1',0} },
112     { {'L','P','T','2',0} },
113     { {'L','P','T','3',0} },
114     { {'L','P','T','4',0} },
115     { {'C','O','M','1',0} },
116     { {'C','O','M','2',0} },
117     { {'C','O','M','3',0} },
118     { {'C','O','M','4',0} }
119 };
120
121 static const WCHAR devW[] = {'\\','D','e','v','i','c','e','\\',0};
122 static const WCHAR dosW[] = {'\\','D','o','s','D','e','v','i','c','e','s','\\',0};
123
124 static const WCHAR auxW[] = {'A','U','X',0};
125 static const WCHAR comW[] = {'C','O','M',0};
126 static const WCHAR lptW[] = {'L','P','T',0};
127 static const WCHAR nulW[] = {'N','U','L',0};
128
129 static const WCHAR nullW[] = {'N','u','l','l',0};
130 static const WCHAR parW[] = {'P','a','r','a','l','l','e','l',0};
131 static const WCHAR serW[] = {'S','e','r','i','a','l',0};
132 static const WCHAR oneW[] = {'1',0};
133
134 /* at some point we may want to allow Winelib apps to set this */
135 static const BOOL is_case_sensitive = FALSE;
136
137 /*
138  * Directory info for DOSFS_ReadDir
139  * contains the names of *all* the files in the directory
140  */
141 typedef struct
142 {
143     int used;
144     int size;
145     WCHAR names[1];
146 } DOS_DIR;
147
148
149 /* return non-zero if c is the end of a directory name */
150 static inline int is_end_of_name(WCHAR c)
151 {
152     return !c || (c == '/') || (c == '\\');
153 }
154
155 /***********************************************************************
156  *           DOSFS_ValidDOSName
157  *
158  * Return 1 if Unix file 'name' is also a valid MS-DOS name
159  * (i.e. contains only valid DOS chars, lower-case only, fits in 8.3 format).
160  * File name can be terminated by '\0', '\\' or '/'.
161  */
162 static int DOSFS_ValidDOSName( LPCWSTR name )
163 {
164     static const char invalid_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" INVALID_DOS_CHARS;
165     const WCHAR *p = name;
166     const char *invalid = !is_case_sensitive ? (invalid_chars + 26) : invalid_chars;
167     int len = 0;
168
169     if (*p == '.')
170     {
171         /* Check for "." and ".." */
172         p++;
173         if (*p == '.') p++;
174         /* All other names beginning with '.' are invalid */
175         return (is_end_of_name(*p));
176     }
177     while (!is_end_of_name(*p))
178     {
179         if (*p < 256 && strchr( invalid, (char)*p )) return 0;  /* Invalid char */
180         if (*p == '.') break;  /* Start of the extension */
181         if (++len > 8) return 0;  /* Name too long */
182         p++;
183     }
184     if (*p != '.') return 1;  /* End of name */
185     p++;
186     if (is_end_of_name(*p)) return 0;  /* Empty extension not allowed */
187     len = 0;
188     while (!is_end_of_name(*p))
189     {
190         if (*p < 256 && strchr( invalid, (char)*p )) return 0;  /* Invalid char */
191         if (*p == '.') return 0;  /* Second extension not allowed */
192         if (++len > 3) return 0;  /* Extension too long */
193         p++;
194     }
195     return 1;
196 }
197
198
199 /***********************************************************************
200  *           DOSFS_ToDosFCBFormat
201  *
202  * Convert a file name to DOS FCB format (8+3 chars, padded with blanks),
203  * expanding wild cards and converting to upper-case in the process.
204  * File name can be terminated by '\0', '\\' or '/'.
205  * Return FALSE if the name is not a valid DOS name.
206  * 'buffer' must be at least 12 characters long.
207  */
208 static BOOL DOSFS_ToDosFCBFormat( LPCWSTR name, LPWSTR buffer )
209 {
210     static const char invalid_chars[] = INVALID_DOS_CHARS;
211     LPCWSTR p = name;
212     int i;
213
214     /* Check for "." and ".." */
215     if (*p == '.')
216     {
217         p++;
218         buffer[0] = '.';
219         for(i = 1; i < 11; i++) buffer[i] = ' ';
220         buffer[11] = 0;
221         if (*p == '.')
222         {
223             buffer[1] = '.';
224             p++;
225         }
226         return (!*p || (*p == '/') || (*p == '\\'));
227     }
228
229     for (i = 0; i < 8; i++)
230     {
231         switch(*p)
232         {
233         case '\0':
234         case '\\':
235         case '/':
236         case '.':
237             buffer[i] = ' ';
238             break;
239         case '?':
240             p++;
241             /* fall through */
242         case '*':
243             buffer[i] = '?';
244             break;
245         default:
246             if (*p < 256 && strchr( invalid_chars, (char)*p )) return FALSE;
247             buffer[i] = toupperW(*p);
248             p++;
249             break;
250         }
251     }
252
253     if (*p == '*')
254     {
255         /* Skip all chars after wildcard up to first dot */
256         while (*p && (*p != '/') && (*p != '\\') && (*p != '.')) p++;
257     }
258     else
259     {
260         /* Check if name too long */
261         if (*p && (*p != '/') && (*p != '\\') && (*p != '.')) return FALSE;
262     }
263     if (*p == '.') p++;  /* Skip dot */
264
265     for (i = 8; i < 11; i++)
266     {
267         switch(*p)
268         {
269         case '\0':
270         case '\\':
271         case '/':
272             buffer[i] = ' ';
273             break;
274         case '.':
275             return FALSE;  /* Second extension not allowed */
276         case '?':
277             p++;
278             /* fall through */
279         case '*':
280             buffer[i] = '?';
281             break;
282         default:
283             if (*p < 256 && strchr( invalid_chars, (char)*p )) return FALSE;
284             buffer[i] = toupperW(*p);
285             p++;
286             break;
287         }
288     }
289     buffer[11] = '\0';
290
291     /* at most 3 character of the extension are processed
292      * is something behind this ?
293      */
294     while (*p == '*' || *p == ' ') p++; /* skip wildcards and spaces */
295     return is_end_of_name(*p);
296 }
297
298
299 /***********************************************************************
300  *           DOSFS_ToDosDTAFormat
301  *
302  * Convert a file name from FCB to DTA format (name.ext, null-terminated)
303  * converting to upper-case in the process.
304  * File name can be terminated by '\0', '\\' or '/'.
305  * 'buffer' must be at least 13 characters long.
306  */
307 static void DOSFS_ToDosDTAFormat( LPCWSTR name, LPWSTR buffer )
308 {
309     LPWSTR p;
310
311     memcpy( buffer, name, 8 * sizeof(WCHAR) );
312     p = buffer + 8;
313     while ((p > buffer) && (p[-1] == ' ')) p--;
314     *p++ = '.';
315     memcpy( p, name + 8, 3 * sizeof(WCHAR) );
316     p += 3;
317     while (p[-1] == ' ') p--;
318     if (p[-1] == '.') p--;
319     *p = '\0';
320 }
321
322
323 /***********************************************************************
324  *           DOSFS_AddDirEntry
325  *
326  *  Used to construct an array of filenames in DOSFS_OpenDir
327  */
328 static BOOL DOSFS_AddDirEntry(DOS_DIR **dir, LPCWSTR name, LPCWSTR dosname)
329 {
330     int extra1 = strlenW(name) + 1;
331     int extra2 = strlenW(dosname) + 1;
332
333     /* if we need more, at minimum double the size */
334     if( (extra1 + extra2 + (*dir)->used) > (*dir)->size)
335     {
336         int more = (*dir)->size;
337         DOS_DIR *t;
338
339         if(more<(extra1+extra2))
340             more = extra1+extra2;
341
342         t = HeapReAlloc(GetProcessHeap(), 0, *dir, sizeof(**dir) + 
343                         ((*dir)->size + more)*sizeof(WCHAR) );
344         if(!t)
345         {
346             SetLastError( ERROR_NOT_ENOUGH_MEMORY );
347             ERR("Out of memory caching directory structure %d %d %d\n",
348                  (*dir)->size, more, (*dir)->used);
349             return FALSE;
350         }
351         (*dir) = t;
352         (*dir)->size += more;
353     }
354
355     /* at this point, the dir structure is big enough to hold these names */
356     strcpyW(&(*dir)->names[(*dir)->used], name);
357     (*dir)->used += extra1;
358     strcpyW(&(*dir)->names[(*dir)->used], dosname);
359     (*dir)->used += extra2;
360
361     return TRUE;
362 }
363
364
365 /***********************************************************************
366  *           DOSFS_OpenDir_VFAT
367  */
368 static BOOL DOSFS_OpenDir_VFAT(DOS_DIR **dir, const char *unix_path)
369 {
370 #ifdef VFAT_IOCTL_READDIR_BOTH
371     KERNEL_DIRENT de[2];
372     int fd = open( unix_path, O_RDONLY|O_DIRECTORY );
373     BOOL r = TRUE;
374
375     /* Check if the VFAT ioctl is supported on this directory */
376
377     if ( fd<0 )
378         return FALSE;
379
380     while (1)
381     {
382         WCHAR long_name[MAX_PATH];
383         WCHAR short_name[12];
384
385         r = (ioctl( fd, VFAT_IOCTL_READDIR_BOTH, (long)de ) != -1);
386         if(!r)
387             break;
388         if (!de[0].d_reclen)
389             break;
390         MultiByteToWideChar(CP_UNIXCP, 0, de[0].d_name, -1, long_name, MAX_PATH);
391         if (!DOSFS_ToDosFCBFormat( long_name, short_name ))
392             short_name[0] = '\0';
393         if (de[1].d_name[0])
394             MultiByteToWideChar(CP_UNIXCP, 0, de[1].d_name, -1, long_name, MAX_PATH);
395         else
396             MultiByteToWideChar(CP_UNIXCP, 0, de[0].d_name, -1, long_name, MAX_PATH);
397         r = DOSFS_AddDirEntry(dir, long_name, short_name );
398         if(!r)
399             break;
400     }
401     if(r)
402     {
403         static const WCHAR empty_strW[] = { 0 };
404         DOSFS_AddDirEntry(dir, empty_strW, empty_strW);
405     }
406     close(fd);
407     return r;
408 #else
409     return FALSE;
410 #endif  /* VFAT_IOCTL_READDIR_BOTH */
411 }
412
413
414 /***********************************************************************
415  *           DOSFS_OpenDir_Normal
416  *
417  * Now use the standard opendir/readdir interface
418  */
419 static BOOL DOSFS_OpenDir_Normal( DOS_DIR **dir, const char *unix_path )
420 {
421     DIR *unixdir = opendir( unix_path );
422     BOOL r = TRUE;
423     static const WCHAR empty_strW[] = { 0 };
424
425     if(!unixdir)
426         return FALSE;
427     while(1)
428     {
429         WCHAR long_name[MAX_PATH];
430         struct dirent *de = readdir(unixdir);
431
432         if(!de)
433             break;
434         MultiByteToWideChar(CP_UNIXCP, 0, de->d_name, -1, long_name, MAX_PATH);
435         r = DOSFS_AddDirEntry(dir, long_name, empty_strW);
436         if(!r)
437             break;
438     }
439     if(r)
440         DOSFS_AddDirEntry(dir, empty_strW, empty_strW);
441     closedir(unixdir);
442     return r;
443 }
444
445 /***********************************************************************
446  *           DOSFS_OpenDir
447  */
448 static DOS_DIR *DOSFS_OpenDir( const char *unix_path )
449 {
450     const int init_size = 0x100;
451     DOS_DIR *dir = HeapAlloc( GetProcessHeap(), 0, sizeof(*dir) + init_size*sizeof (WCHAR));
452     BOOL r;
453
454     TRACE("%s\n",debugstr_a(unix_path));
455
456     if (!dir)
457     {
458         SetLastError( ERROR_NOT_ENOUGH_MEMORY );
459         return NULL;
460     }
461     dir->used = 0;
462     dir->size = init_size;
463
464     /* Treat empty path as root directory. This simplifies path split into
465        directory and mask in several other places */
466     if (!*unix_path) unix_path = "/";
467
468     r = DOSFS_OpenDir_VFAT( &dir, unix_path);
469
470     if(!r)
471         r = DOSFS_OpenDir_Normal( &dir, unix_path);
472
473     if(!r)
474     {
475         HeapFree(GetProcessHeap(), 0, dir);
476         return NULL;
477     }
478     dir->used = 0;
479
480     return dir;
481 }
482
483
484 /***********************************************************************
485  *           DOSFS_CloseDir
486  */
487 static void DOSFS_CloseDir( DOS_DIR *dir )
488 {
489     HeapFree( GetProcessHeap(), 0, dir );
490 }
491
492
493 /***********************************************************************
494  *           DOSFS_ReadDir
495  */
496 static BOOL DOSFS_ReadDir( DOS_DIR *dir, LPCWSTR *long_name,
497                              LPCWSTR *short_name )
498 {
499     LPCWSTR sn, ln;
500
501     if (!dir)
502        return FALSE;
503
504     /* the long pathname is first */
505     ln = &dir->names[dir->used];
506     if(ln[0])
507         *long_name  = ln;
508     else
509         return FALSE;
510     dir->used += (strlenW(ln) + 1);
511
512     /* followed by the short path name */
513     sn = &dir->names[dir->used];
514     if(sn[0])
515         *short_name = sn;
516     else
517         *short_name = NULL;
518     dir->used += (strlenW(sn) + 1);
519
520     return TRUE;
521 }
522
523
524 /***********************************************************************
525  *           DOSFS_Hash
526  *
527  * Transform a Unix file name into a hashed DOS name. If the name is a valid
528  * DOS name, it is converted to upper-case; otherwise it is replaced by a
529  * hashed version that fits in 8.3 format.
530  * File name can be terminated by '\0', '\\' or '/'.
531  * 'buffer' must be at least 13 characters long.
532  */
533 static void DOSFS_Hash( LPCWSTR name, LPWSTR buffer, BOOL dir_format )
534 {
535     static const char invalid_chars[] = INVALID_DOS_CHARS "~.";
536     static const char hash_chars[32] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345";
537
538     LPCWSTR p, ext;
539     LPWSTR dst;
540     unsigned short hash;
541     int i;
542
543     if (dir_format)
544     {
545         for(i = 0; i < 11; i++) buffer[i] = ' ';
546         buffer[11] = 0;
547     }
548
549     if (DOSFS_ValidDOSName( name ))
550     {
551         /* Check for '.' and '..' */
552         if (*name == '.')
553         {
554             buffer[0] = '.';
555             if (!dir_format) buffer[1] = buffer[2] = '\0';
556             if (name[1] == '.') buffer[1] = '.';
557             return;
558         }
559
560         /* Simply copy the name, converting to uppercase */
561
562         for (dst = buffer; !is_end_of_name(*name) && (*name != '.'); name++)
563             *dst++ = toupperW(*name);
564         if (*name == '.')
565         {
566             if (dir_format) dst = buffer + 8;
567             else *dst++ = '.';
568             for (name++; !is_end_of_name(*name); name++)
569                 *dst++ = toupperW(*name);
570         }
571         if (!dir_format) *dst = '\0';
572         return;
573     }
574
575     /* Compute the hash code of the file name */
576     /* If you know something about hash functions, feel free to */
577     /* insert a better algorithm here... */
578     if (!is_case_sensitive)
579     {
580         for (p = name, hash = 0xbeef; !is_end_of_name(p[1]); p++)
581             hash = (hash<<3) ^ (hash>>5) ^ tolowerW(*p) ^ (tolowerW(p[1]) << 8);
582         hash = (hash<<3) ^ (hash>>5) ^ tolowerW(*p); /* Last character */
583     }
584     else
585     {
586         for (p = name, hash = 0xbeef; !is_end_of_name(p[1]); p++)
587             hash = (hash << 3) ^ (hash >> 5) ^ *p ^ (p[1] << 8);
588         hash = (hash << 3) ^ (hash >> 5) ^ *p;  /* Last character */
589     }
590
591     /* Find last dot for start of the extension */
592     for (p = name+1, ext = NULL; !is_end_of_name(*p); p++)
593         if (*p == '.') ext = p;
594     if (ext && is_end_of_name(ext[1]))
595         ext = NULL;  /* Empty extension ignored */
596
597     /* Copy first 4 chars, replacing invalid chars with '_' */
598     for (i = 4, p = name, dst = buffer; i > 0; i--, p++)
599     {
600         if (is_end_of_name(*p) || (p == ext)) break;
601         *dst++ = (*p < 256 && strchr( invalid_chars, (char)*p )) ? '_' : toupperW(*p);
602     }
603     /* Pad to 5 chars with '~' */
604     while (i-- >= 0) *dst++ = '~';
605
606     /* Insert hash code converted to 3 ASCII chars */
607     *dst++ = hash_chars[(hash >> 10) & 0x1f];
608     *dst++ = hash_chars[(hash >> 5) & 0x1f];
609     *dst++ = hash_chars[hash & 0x1f];
610
611     /* Copy the first 3 chars of the extension (if any) */
612     if (ext)
613     {
614         if (!dir_format) *dst++ = '.';
615         for (i = 3, ext++; (i > 0) && !is_end_of_name(*ext); i--, ext++)
616             *dst++ = (*ext < 256 && strchr( invalid_chars, (char)*ext )) ? '_' : toupperW(*ext);
617     }
618     if (!dir_format) *dst = '\0';
619 }
620
621
622 /***********************************************************************
623  *           DOSFS_FindUnixName
624  *
625  * Find the Unix file name in a given directory that corresponds to
626  * a file name (either in Unix or DOS format).
627  * File name can be terminated by '\0', '\\' or '/'.
628  * Return TRUE if OK, FALSE if no file name matches.
629  *
630  * 'long_buf' must be at least 'long_len' characters long. If the long name
631  * turns out to be larger than that, the function returns FALSE.
632  * 'short_buf' must be at least 13 characters long.
633  */
634 BOOL DOSFS_FindUnixName( const DOS_FULL_NAME *path, LPCWSTR name, char *long_buf,
635                          INT long_len, LPWSTR short_buf )
636 {
637     DOS_DIR *dir;
638     LPCWSTR long_name, short_name;
639     WCHAR dos_name[12], tmp_buf[13];
640     BOOL ret;
641
642     LPCWSTR p = strchrW( name, '/' );
643     int len = p ? (int)(p - name) : strlenW(name);
644     if ((p = strchrW( name, '\\' ))) len = min( (int)(p - name), len );
645     /* Ignore trailing dots and spaces */
646     while (len > 1 && (name[len-1] == '.' || name[len-1] == ' ')) len--;
647     if (long_len < len + 1) return FALSE;
648
649     TRACE("%s,%s\n", path->long_name, debugstr_w(name) );
650
651     if (!DOSFS_ToDosFCBFormat( name, dos_name )) dos_name[0] = '\0';
652
653     if (!(dir = DOSFS_OpenDir( path->long_name )))
654     {
655         WARN("(%s,%s): can't open dir: %s\n",
656              path->long_name, debugstr_w(name), strerror(errno) );
657         return FALSE;
658     }
659
660     while ((ret = DOSFS_ReadDir( dir, &long_name, &short_name )))
661     {
662         /* Check against Unix name */
663         if (len == strlenW(long_name))
664         {
665             if (is_case_sensitive)
666             {
667                 if (!strncmpW( long_name, name, len )) break;
668             }
669             else
670             {
671                 if (!strncmpiW( long_name, name, len )) break;
672             }
673         }
674         if (dos_name[0])
675         {
676             /* Check against hashed DOS name */
677             if (!short_name)
678             {
679                 DOSFS_Hash( long_name, tmp_buf, TRUE );
680                 short_name = tmp_buf;
681             }
682             if (!strcmpW( dos_name, short_name )) break;
683         }
684     }
685     if (ret)
686     {
687         if (long_buf) WideCharToMultiByte(CP_UNIXCP, 0, long_name, -1, long_buf, long_len, NULL, NULL);
688         if (short_buf)
689         {
690             if (short_name)
691                 DOSFS_ToDosDTAFormat( short_name, short_buf );
692             else
693                 DOSFS_Hash( long_name, short_buf, FALSE );
694         }
695         TRACE("(%s,%s) -> %s (%s)\n", path->long_name, debugstr_w(name),
696               debugstr_w(long_name), short_buf ? debugstr_w(short_buf) : "***");
697     }
698     else
699         WARN("%s not found in '%s'\n", debugstr_w(name), path->long_name);
700     DOSFS_CloseDir( dir );
701     return ret;
702 }
703
704
705 /**************************************************************************
706  *         DOSFS_CreateCommPort
707  */
708 static HANDLE DOSFS_CreateCommPort(LPCWSTR name, DWORD access, DWORD attributes, LPSECURITY_ATTRIBUTES sa)
709 {
710     HANDLE ret;
711     HKEY hkey;
712     DWORD dummy;
713     OBJECT_ATTRIBUTES attr;
714     UNICODE_STRING nameW;
715     WCHAR *devnameW;
716     char tmp[128];
717     char devname[40];
718
719     static const WCHAR serialportsW[] = {'M','a','c','h','i','n','e','\\',
720                                          'S','o','f','t','w','a','r','e','\\',
721                                          'W','i','n','e','\\','W','i','n','e','\\',
722                                          'C','o','n','f','i','g','\\',
723                                          'S','e','r','i','a','l','P','o','r','t','s',0};
724
725     TRACE_(file)("%s %lx %lx\n", debugstr_w(name), access, attributes);
726
727     attr.Length = sizeof(attr);
728     attr.RootDirectory = 0;
729     attr.ObjectName = &nameW;
730     attr.Attributes = 0;
731     attr.SecurityDescriptor = NULL;
732     attr.SecurityQualityOfService = NULL;
733     RtlInitUnicodeString( &nameW, serialportsW );
734
735     if (NtOpenKey( &hkey, KEY_ALL_ACCESS, &attr )) return 0;
736
737     RtlInitUnicodeString( &nameW, name );
738     if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
739         devnameW = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
740     else
741         devnameW = NULL;
742
743     NtClose( hkey );
744
745     if (!devnameW) return 0;
746     WideCharToMultiByte(CP_ACP, 0, devnameW, -1, devname, sizeof(devname), NULL, NULL);
747
748     TRACE("opening %s as %s\n", devname, debugstr_w(name));
749
750     SERVER_START_REQ( create_serial )
751     {
752         req->access  = access;
753         req->inherit = (sa && (sa->nLength>=sizeof(*sa)) && sa->bInheritHandle);
754         req->attributes = attributes;
755         req->sharing = FILE_SHARE_READ|FILE_SHARE_WRITE;
756         wine_server_add_data( req, devname, strlen(devname) );
757         SetLastError(0);
758         wine_server_call_err( req );
759         ret = reply->handle;
760     }
761     SERVER_END_REQ;
762
763     if(!ret)
764         ERR("Couldn't open device '%s' ! (check permissions)\n",devname);
765     else
766         TRACE("return %p\n", ret );
767     return ret;
768 }
769
770 /***********************************************************************
771  *           DOSFS_OpenDevice
772  *
773  * Open a DOS device. This might not map 1:1 into the UNIX device concept.
774  * Returns 0 on failure.
775  */
776 HANDLE DOSFS_OpenDevice( LPCWSTR name, DWORD access, DWORD attributes, LPSECURITY_ATTRIBUTES sa )
777 {
778     unsigned int i;
779     const WCHAR *p;
780     HANDLE handle;
781
782     if (name[0] && (name[1] == ':')) name += 2;
783     if ((p = strrchrW( name, '/' ))) name = p + 1;
784     if ((p = strrchrW( name, '\\' ))) name = p + 1;
785     for (i = 0; i < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0]); i++)
786     {
787         const WCHAR *dev = DOSFS_Devices[i].name;
788         if (!strncmpiW( dev, name, strlenW(dev) ))
789         {
790             p = name + strlenW( dev );
791             if (!*p || (*p == '.') || (*p == ':')) {
792                 static const WCHAR nulW[] = {'N','U','L',0};
793                 static const WCHAR conW[] = {'C','O','N',0};
794                 /* got it */
795                 if (!strcmpiW(DOSFS_Devices[i].name, nulW))
796                     return FILE_CreateFile( "/dev/null", access,
797                                             FILE_SHARE_READ|FILE_SHARE_WRITE, sa,
798                                             OPEN_EXISTING, 0, 0, TRUE, DRIVE_UNKNOWN );
799                 if (!strcmpiW(DOSFS_Devices[i].name, conW)) {
800                         HANDLE to_dup;
801                         switch (access & (GENERIC_READ|GENERIC_WRITE)) {
802                         case GENERIC_READ:
803                                 to_dup = GetStdHandle( STD_INPUT_HANDLE );
804                                 break;
805                         case GENERIC_WRITE:
806                                 to_dup = GetStdHandle( STD_OUTPUT_HANDLE );
807                                 break;
808                         default:
809                                 FIXME("can't open CON read/write\n");
810                                 return 0;
811                         }
812                         if (!DuplicateHandle( GetCurrentProcess(), to_dup, GetCurrentProcess(),
813                                               &handle, 0,
814                                               sa && (sa->nLength>=sizeof(*sa)) && sa->bInheritHandle,
815                                               DUPLICATE_SAME_ACCESS ))
816                             handle = 0;
817                         return handle;
818                 }
819
820                 if( (handle=DOSFS_CreateCommPort(DOSFS_Devices[i].name,access,attributes,sa)) )
821                     return handle;
822                 FIXME("device open %s not supported (yet)\n", debugstr_w(DOSFS_Devices[i].name));
823                 return 0;
824             }
825         }
826     }
827     return 0;
828 }
829
830
831 /***********************************************************************
832  *           DOSFS_GetPathDrive
833  *
834  * Get the drive specified by a given path name (DOS or Unix format).
835  */
836 static int DOSFS_GetPathDrive( LPCWSTR *name )
837 {
838     int drive;
839     LPCWSTR p = *name;
840
841     if (*p && (p[1] == ':'))
842     {
843         drive = toupperW(*p) - 'A';
844         *name += 2;
845     }
846     else if (*p == '/') /* Absolute Unix path? */
847     {
848         if ((drive = DRIVE_FindDriveRootW( name )) == -1)
849         {
850             MESSAGE("Warning: %s not accessible from a configured DOS drive\n", debugstr_w(*name) );
851             /* Assume it really was a DOS name */
852             drive = DRIVE_GetCurrentDrive();
853         }
854     }
855     else drive = DRIVE_GetCurrentDrive();
856
857     if (!DRIVE_IsValid(drive))
858     {
859         SetLastError( ERROR_INVALID_DRIVE );
860         return -1;
861     }
862     return drive;
863 }
864
865
866 /***********************************************************************
867  *           DOSFS_GetFullName
868  *
869  * Convert a file name (DOS or mixed DOS/Unix format) to a valid
870  * Unix name / short DOS name pair.
871  * Return FALSE if one of the path components does not exist. The last path
872  * component is only checked if 'check_last' is non-zero.
873  * The buffers pointed to by 'long_buf' and 'short_buf' must be
874  * at least MAX_PATHNAME_LEN long.
875  */
876 BOOL DOSFS_GetFullName( LPCWSTR name, BOOL check_last, DOS_FULL_NAME *full )
877 {
878     BOOL found;
879     char *p_l, *root;
880     LPWSTR p_s;
881     static const WCHAR driveA_rootW[] = {'A',':','\\',0};
882     static const WCHAR dos_rootW[] = {'\\',0};
883
884     TRACE("%s (last=%d)\n", debugstr_w(name), check_last );
885
886     if ((!*name) || (*name=='\n'))
887     { /* error code for Win98 */
888         SetLastError(ERROR_BAD_PATHNAME);
889         return FALSE;
890     }
891
892     if ((full->drive = DOSFS_GetPathDrive( &name )) == -1) return FALSE;
893
894     lstrcpynA( full->long_name, DRIVE_GetRoot( full->drive ),
895                  sizeof(full->long_name) );
896     if (full->long_name[1]) root = full->long_name + strlen(full->long_name);
897     else root = full->long_name;  /* root directory */
898
899     strcpyW( full->short_name, driveA_rootW );
900     full->short_name[0] += full->drive;
901
902     if ((*name == '\\') || (*name == '/'))  /* Absolute path */
903     {
904         while ((*name == '\\') || (*name == '/')) name++;
905     }
906     else  /* Relative path */
907     {
908         lstrcpynA( root + 1, DRIVE_GetUnixCwd( full->drive ),
909                      sizeof(full->long_name) - (root - full->long_name) - 1 );
910         if (root[1]) *root = '/';
911         lstrcpynW( full->short_name + 3, DRIVE_GetDosCwd( full->drive ),
912                    sizeof(full->short_name)/sizeof(full->short_name[0]) - 3 );
913     }
914
915     p_l = full->long_name[1] ? full->long_name + strlen(full->long_name)
916                              : full->long_name;
917     p_s = full->short_name[3] ? full->short_name + strlenW(full->short_name)
918                               : full->short_name + 2;
919     found = TRUE;
920
921     while (*name && found)
922     {
923         /* Check for '.' and '..' */
924
925         if (*name == '.')
926         {
927             if (is_end_of_name(name[1]))
928             {
929                 name++;
930                 while ((*name == '\\') || (*name == '/')) name++;
931                 continue;
932             }
933             else if ((name[1] == '.') && is_end_of_name(name[2]))
934             {
935                 name += 2;
936                 while ((*name == '\\') || (*name == '/')) name++;
937                 while ((p_l > root) && (*p_l != '/')) p_l--;
938                 while ((p_s > full->short_name + 2) && (*p_s != '\\')) p_s--;
939                 *p_l = *p_s = '\0';  /* Remove trailing separator */
940                 continue;
941             }
942         }
943
944         /* Make sure buffers are large enough */
945
946         if ((p_s >= full->short_name + sizeof(full->short_name)/sizeof(full->short_name[0]) - 14) ||
947             (p_l >= full->long_name + sizeof(full->long_name) - 1))
948         {
949             SetLastError( ERROR_PATH_NOT_FOUND );
950             return FALSE;
951         }
952
953         /* Get the long and short name matching the file name */
954
955         if ((found = DOSFS_FindUnixName( full, name, p_l + 1,
956                          sizeof(full->long_name) - (p_l - full->long_name) - 1, p_s + 1 )))
957         {
958             *p_l++ = '/';
959             p_l   += strlen(p_l);
960             *p_s++ = '\\';
961             p_s   += strlenW(p_s);
962             while (!is_end_of_name(*name)) name++;
963         }
964         else if (!check_last)
965         {
966             *p_l++ = '/';
967             *p_s++ = '\\';
968             while (!is_end_of_name(*name) &&
969                    (p_s < full->short_name + sizeof(full->short_name)/sizeof(full->short_name[0]) - 1) &&
970                    (p_l < full->long_name + sizeof(full->long_name) - 1))
971             {
972                 WCHAR wch;
973                 *p_s++ = tolowerW(*name);
974                 /* If the drive is case-sensitive we want to create new */
975                 /* files in lower-case otherwise we can't reopen them   */
976                 /* under the same short name. */
977                 if (is_case_sensitive) wch = tolowerW(*name);
978                 else wch = *name;
979                 p_l += WideCharToMultiByte(CP_UNIXCP, 0, &wch, 1, p_l, 2, NULL, NULL);
980                 name++;
981             }
982             /* Ignore trailing dots and spaces */
983             while(p_l[-1] == '.' || p_l[-1] == ' ') {
984                 --p_l;
985                 --p_s;
986             }
987             *p_l = '\0';
988             *p_s = '\0';
989         }
990         while ((*name == '\\') || (*name == '/')) name++;
991     }
992
993     if (!found)
994     {
995         if (check_last)
996         {
997             SetLastError( ERROR_FILE_NOT_FOUND );
998             return FALSE;
999         }
1000         if (*name)  /* Not last */
1001         {
1002             SetLastError( ERROR_PATH_NOT_FOUND );
1003             return FALSE;
1004         }
1005     }
1006     if (!full->long_name[0]) strcpy( full->long_name, "/" );
1007     if (!full->short_name[2]) strcpyW( full->short_name + 2, dos_rootW );
1008     TRACE("returning %s = %s\n", full->long_name, debugstr_w(full->short_name) );
1009     return TRUE;
1010 }
1011
1012
1013 /***********************************************************************
1014  *           wine_get_unix_file_name (KERNEL32.@) Not a Windows API
1015  *
1016  * Return the full Unix file name for a given path.
1017  */
1018 BOOL WINAPI wine_get_unix_file_name( LPCWSTR dosW, LPSTR buffer, DWORD len )
1019 {
1020     BOOL ret;
1021     DOS_FULL_NAME path;
1022
1023     ret = DOSFS_GetFullName( dosW, FALSE, &path );
1024     if (ret && len)
1025     {
1026         strncpy( buffer, path.long_name, len );
1027         buffer[len - 1] = 0; /* ensure 0 termination */
1028     }
1029     return ret;
1030 }
1031
1032
1033 /***********************************************************************
1034  *           MulDiv   (KERNEL32.@)
1035  * RETURNS
1036  *      Result of multiplication and division
1037  *      -1: Overflow occurred or Divisor was 0
1038  */
1039 INT WINAPI MulDiv(
1040              INT nMultiplicand,
1041              INT nMultiplier,
1042              INT nDivisor)
1043 {
1044     LONGLONG ret;
1045
1046     if (!nDivisor) return -1;
1047
1048     /* We want to deal with a positive divisor to simplify the logic. */
1049     if (nDivisor < 0)
1050     {
1051       nMultiplicand = - nMultiplicand;
1052       nDivisor = -nDivisor;
1053     }
1054
1055     /* If the result is positive, we "add" to round. else, we subtract to round. */
1056     if ( ( (nMultiplicand <  0) && (nMultiplier <  0) ) ||
1057          ( (nMultiplicand >= 0) && (nMultiplier >= 0) ) )
1058       ret = (((LONGLONG)nMultiplicand * nMultiplier) + (nDivisor/2)) / nDivisor;
1059     else
1060       ret = (((LONGLONG)nMultiplicand * nMultiplier) - (nDivisor/2)) / nDivisor;
1061
1062     if ((ret > 2147483647) || (ret < -2147483647)) return -1;
1063     return ret;
1064 }
1065
1066
1067 /***********************************************************************
1068  *           DosDateTimeToFileTime   (KERNEL32.@)
1069  */
1070 BOOL WINAPI DosDateTimeToFileTime( WORD fatdate, WORD fattime, LPFILETIME ft)
1071 {
1072     struct tm newtm;
1073 #ifndef HAVE_TIMEGM
1074     struct tm *gtm;
1075     time_t time1, time2;
1076 #endif
1077
1078     newtm.tm_sec  = (fattime & 0x1f) * 2;
1079     newtm.tm_min  = (fattime >> 5) & 0x3f;
1080     newtm.tm_hour = (fattime >> 11);
1081     newtm.tm_mday = (fatdate & 0x1f);
1082     newtm.tm_mon  = ((fatdate >> 5) & 0x0f) - 1;
1083     newtm.tm_year = (fatdate >> 9) + 80;
1084 #ifdef HAVE_TIMEGM
1085     RtlSecondsSince1970ToTime( timegm(&newtm), (LARGE_INTEGER *)ft );
1086 #else
1087     time1 = mktime(&newtm);
1088     gtm = gmtime(&time1);
1089     time2 = mktime(gtm);
1090     RtlSecondsSince1970ToTime( 2*time1-time2, (LARGE_INTEGER *)ft );
1091 #endif
1092     return TRUE;
1093 }
1094
1095
1096 /***********************************************************************
1097  *           FileTimeToDosDateTime   (KERNEL32.@)
1098  */
1099 BOOL WINAPI FileTimeToDosDateTime( const FILETIME *ft, LPWORD fatdate,
1100                                      LPWORD fattime )
1101 {
1102     LARGE_INTEGER       li;
1103     ULONG               t;
1104     time_t              unixtime;
1105     struct tm*          tm;
1106
1107     li.u.LowPart = ft->dwLowDateTime;
1108     li.u.HighPart = ft->dwHighDateTime;
1109     RtlTimeToSecondsSince1970( &li, &t );
1110     unixtime = t;
1111     tm = gmtime( &unixtime );
1112     if (fattime)
1113         *fattime = (tm->tm_hour << 11) + (tm->tm_min << 5) + (tm->tm_sec / 2);
1114     if (fatdate)
1115         *fatdate = ((tm->tm_year - 80) << 9) + ((tm->tm_mon + 1) << 5)
1116                    + tm->tm_mday;
1117     return TRUE;
1118 }
1119
1120
1121 /***********************************************************************
1122  *           QueryDosDeviceA   (KERNEL32.@)
1123  *
1124  * returns array of strings terminated by \0, terminated by \0
1125  */
1126 DWORD WINAPI QueryDosDeviceA(LPCSTR devname,LPSTR target,DWORD bufsize)
1127 {
1128     DWORD ret = 0, retW;
1129     LPWSTR targetW = (LPWSTR)HeapAlloc(GetProcessHeap(),0,
1130                                      bufsize * sizeof(WCHAR));
1131     UNICODE_STRING devnameW;
1132
1133     if(devname) RtlCreateUnicodeStringFromAsciiz(&devnameW, devname);
1134     else devnameW.Buffer = NULL;
1135
1136     retW = QueryDosDeviceW(devnameW.Buffer, targetW, bufsize);
1137
1138     ret = WideCharToMultiByte(CP_ACP, 0, targetW, retW, target,
1139                                         bufsize, NULL, NULL);
1140
1141     RtlFreeUnicodeString(&devnameW);
1142     if (targetW) HeapFree(GetProcessHeap(),0,targetW);
1143     return ret;
1144 }
1145
1146
1147 /***********************************************************************
1148  *           QueryDosDeviceW   (KERNEL32.@)
1149  *
1150  * returns array of strings terminated by \0, terminated by \0
1151  *
1152  * FIXME
1153  *      - Win9x returns for all calls ERROR_INVALID_PARAMETER 
1154  *      - the returned devices for devname == NULL is far from complete
1155  *      - its not checked that the returned device exist
1156  */
1157 DWORD WINAPI QueryDosDeviceW(LPCWSTR devname,LPWSTR target,DWORD bufsize)
1158 {
1159     const WCHAR *pDev, *pName, *pNum = NULL;
1160     int    numsiz=0;
1161     DWORD  ret;
1162
1163     TRACE("(%s,...)\n", debugstr_w(devname));
1164     if (!devname) {
1165         /* return known MSDOS devices */
1166         DWORD ret = 0;
1167         int i;
1168         static const WCHAR devices[][5] = {{'A','U','X',0},
1169                                            {'C','O','M','1',0},
1170                                            {'C','O','M','2',0},
1171                                            {'L','P','T','1',0},
1172                                            {'N','U','L',0,}};
1173         for(i=0; (i< (sizeof(devices)/sizeof(devices[0]))); i++) {
1174             DWORD len = strlenW(devices[i]);
1175             if(target && (bufsize >= ret + len + 2)) {
1176                 strcpyW(target+ret, devices[i]);
1177                 ret += len + 1;
1178             } else {
1179                 /* in this case WinXP returns 0 */
1180                 FIXME("function return is wrong for WinXP!\n");
1181                 SetLastError(ERROR_INSUFFICIENT_BUFFER);
1182                 break;
1183             }
1184         }
1185         /* append drives here */
1186         if(target && bufsize > 0) target[ret++] = 0;
1187         FIXME("Returned list is not complete\n");
1188         return ret;
1189     }
1190     /* In theory all that are possible and have been defined.
1191      * Now just those below, since mirc uses it to check for special files.
1192      *
1193      * (It is more complex, and supports netmounted stuff, and \\.\ stuff,
1194      *  but currently we just ignore that.)
1195      */
1196     if (!strcmpiW(devname, auxW)) {
1197         pDev   = dosW;
1198         pName  = comW;
1199         numsiz = 1;
1200         pNum   = oneW;
1201     } else if (!strcmpiW(devname, nulW)) {
1202         pDev  = devW;
1203         pName = nullW;
1204     } else if (!strncmpiW(devname, comW, strlenW(comW))) {
1205         pDev  = devW;
1206         pName = serW;
1207         pNum  = devname + strlenW(comW);
1208         for(numsiz=0; isdigitW(*(pNum+numsiz)); numsiz++);
1209         if(*(pNum + numsiz)) {
1210             SetLastError(ERROR_FILE_NOT_FOUND);
1211             return 0;
1212         }
1213     } else if (!strncmpiW(devname, lptW, strlenW(lptW))) {
1214         pDev  = devW;
1215         pName = parW;
1216         pNum  = devname + strlenW(lptW);
1217         for(numsiz=0; isdigitW(*(pNum+numsiz)); numsiz++);
1218         if(*(pNum + numsiz)) {
1219             SetLastError(ERROR_FILE_NOT_FOUND);
1220         return 0;
1221     }
1222     } else {
1223         /* This might be a DOS device we do not handle yet ... */
1224         FIXME("(%s) not detected as DOS device!\n",debugstr_w(devname));
1225
1226         /* Win9x set the error ERROR_INVALID_PARAMETER */
1227         SetLastError(ERROR_FILE_NOT_FOUND);
1228         return 0;
1229 }
1230     FIXME("device %s may not exist on this computer\n", debugstr_w(devname));
1231
1232     ret = strlenW(pDev) + strlenW(pName) + numsiz + 2;
1233     if (ret > bufsize) ret = 0;
1234     if (target && ret) {
1235         strcpyW(target,pDev);
1236         strcatW(target,pName);
1237         if (pNum) strcatW(target,pNum);
1238         target[ret-1] = 0;
1239     }
1240     return ret;
1241 }