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