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