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