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