Fixed documentation.
[wine] / files / dos_fs.c
1 /*
2  * DOS file system functions
3  *
4  * Copyright 1993 Erik Bos
5  * Copyright 1996 Alexandre Julliard
6  */
7
8 #include "config.h"
9 #include <sys/types.h>
10 #include <ctype.h>
11 #include <dirent.h>
12 #include <errno.h>
13 #ifdef HAVE_SYS_ERRNO_H
14 #include <sys/errno.h>
15 #endif
16 #include <fcntl.h>
17 #include <string.h>
18 #include <stdlib.h>
19 #include <sys/stat.h>
20 #include <sys/ioctl.h>
21 #include <time.h>
22 #include <unistd.h>
23
24 #include "windef.h"
25 #include "winuser.h"
26 #include "wine/winbase16.h"
27 #include "winerror.h"
28 #include "drive.h"
29 #include "file.h"
30 #include "heap.h"
31 #include "msdos.h"
32 #include "syslevel.h"
33 #include "server.h"
34 #include "process.h"
35 #include "options.h"
36 #include "debugtools.h"
37
38 DECLARE_DEBUG_CHANNEL(dosfs)
39 DECLARE_DEBUG_CHANNEL(file)
40
41 /* Define the VFAT ioctl to get both short and long file names */
42 /* FIXME: is it possible to get this to work on other systems? */
43 #ifdef linux
44 /* We want the real kernel dirent structure, not the libc one */
45 typedef struct
46 {
47     long d_ino;
48     long d_off;
49     unsigned short d_reclen;
50     char d_name[256];
51 } KERNEL_DIRENT;
52
53 #define VFAT_IOCTL_READDIR_BOTH  _IOR('r', 1, KERNEL_DIRENT [2] )
54
55 #else   /* linux */
56 #undef VFAT_IOCTL_READDIR_BOTH  /* just in case... */
57 #endif  /* linux */
58
59 /* Chars we don't want to see in DOS file names */
60 #define INVALID_DOS_CHARS  "*?<>|\"+=,;[] \345"
61
62 static const DOS_DEVICE DOSFS_Devices[] =
63 /* name, device flags (see Int 21/AX=0x4400) */
64 {
65     { "CON",            0xc0d3 },
66     { "PRN",            0xa0c0 },
67     { "NUL",            0x80c4 },
68     { "AUX",            0x80c0 },
69     { "LPT1",           0xa0c0 },
70     { "LPT2",           0xa0c0 },
71     { "LPT3",           0xa0c0 },
72     { "LPT4",           0xc0d3 },
73     { "COM1",           0x80c0 },
74     { "COM2",           0x80c0 },
75     { "COM3",           0x80c0 },
76     { "COM4",           0x80c0 },
77     { "SCSIMGR$",       0xc0c0 },
78     { "HPSCAN",         0xc0c0 }
79 };
80
81 #define GET_DRIVE(path) \
82     (((path)[1] == ':') ? toupper((path)[0]) - 'A' : DOSFS_CurDrive)
83
84 /* Directory info for DOSFS_ReadDir */
85 typedef struct
86 {
87     DIR           *dir;
88 #ifdef VFAT_IOCTL_READDIR_BOTH
89     int            fd;
90     char           short_name[12];
91     KERNEL_DIRENT  dirent[2];
92 #endif
93 } DOS_DIR;
94
95 /* Info structure for FindFirstFile handle */
96 typedef struct
97 {
98     LPSTR path;
99     LPSTR long_mask;
100     LPSTR short_mask;
101     BYTE  attr;
102     int   drive;
103     int   cur_pos;
104     DOS_DIR *dir;
105 } FIND_FIRST_INFO;
106
107
108
109 /***********************************************************************
110  *           DOSFS_ValidDOSName
111  *
112  * Return 1 if Unix file 'name' is also a valid MS-DOS name
113  * (i.e. contains only valid DOS chars, lower-case only, fits in 8.3 format).
114  * File name can be terminated by '\0', '\\' or '/'.
115  */
116 static int DOSFS_ValidDOSName( const char *name, int ignore_case )
117 {
118     static const char invalid_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" INVALID_DOS_CHARS;
119     const char *p = name;
120     const char *invalid = ignore_case ? (invalid_chars + 26) : invalid_chars;
121     int len = 0;
122
123     if (*p == '.')
124     {
125         /* Check for "." and ".." */
126         p++;
127         if (*p == '.') p++;
128         /* All other names beginning with '.' are invalid */
129         return (IS_END_OF_NAME(*p));
130     }
131     while (!IS_END_OF_NAME(*p))
132     {
133         if (strchr( invalid, *p )) return 0;  /* Invalid char */
134         if (*p == '.') break;  /* Start of the extension */
135         if (++len > 8) return 0;  /* Name too long */
136         p++;
137     }
138     if (*p != '.') return 1;  /* End of name */
139     p++;
140     if (IS_END_OF_NAME(*p)) return 0;  /* Empty extension not allowed */
141     len = 0;
142     while (!IS_END_OF_NAME(*p))
143     {
144         if (strchr( invalid, *p )) return 0;  /* Invalid char */
145         if (*p == '.') return 0;  /* Second extension not allowed */
146         if (++len > 3) return 0;  /* Extension too long */
147         p++;
148     }
149     return 1;
150 }
151
152
153 /***********************************************************************
154  *           DOSFS_ToDosFCBFormat
155  *
156  * Convert a file name to DOS FCB format (8+3 chars, padded with blanks),
157  * expanding wild cards and converting to upper-case in the process.
158  * File name can be terminated by '\0', '\\' or '/'.
159  * Return FALSE if the name is not a valid DOS name.
160  * 'buffer' must be at least 12 characters long.
161  */
162 BOOL DOSFS_ToDosFCBFormat( LPCSTR name, LPSTR buffer )
163 {
164     static const char invalid_chars[] = INVALID_DOS_CHARS;
165     const char *p = name;
166     int i;
167
168     /* Check for "." and ".." */
169     if (*p == '.')
170     {
171         p++;
172         strcpy( buffer, ".          " );
173         if (*p == '.')
174         {
175             buffer[1] = '.';
176             p++;
177         }
178         return (!*p || (*p == '/') || (*p == '\\'));
179     }
180
181     for (i = 0; i < 8; i++)
182     {
183         switch(*p)
184         {
185         case '\0':
186         case '\\':
187         case '/':
188         case '.':
189             buffer[i] = ' ';
190             break;
191         case '?':
192             p++;
193             /* fall through */
194         case '*':
195             buffer[i] = '?';
196             break;
197         default:
198             if (strchr( invalid_chars, *p )) return FALSE;
199             buffer[i] = toupper(*p);
200             p++;
201             break;
202         }
203     }
204
205     if (*p == '*')
206     {
207         /* Skip all chars after wildcard up to first dot */
208         while (*p && (*p != '/') && (*p != '\\') && (*p != '.')) p++;
209     }
210     else
211     {
212         /* Check if name too long */
213         if (*p && (*p != '/') && (*p != '\\') && (*p != '.')) return FALSE;
214     }
215     if (*p == '.') p++;  /* Skip dot */
216
217     for (i = 8; i < 11; i++)
218     {
219         switch(*p)
220         {
221         case '\0':
222         case '\\':
223         case '/':
224             buffer[i] = ' ';
225             break;
226         case '.':
227             return FALSE;  /* Second extension not allowed */
228         case '?':
229             p++;
230             /* fall through */
231         case '*':
232             buffer[i] = '?';
233             break;
234         default:
235             if (strchr( invalid_chars, *p )) return FALSE;
236             buffer[i] = toupper(*p);
237             p++;
238             break;
239         }
240     }
241     buffer[11] = '\0';
242     return TRUE;
243 }
244
245
246 /***********************************************************************
247  *           DOSFS_ToDosDTAFormat
248  *
249  * Convert a file name from FCB to DTA format (name.ext, null-terminated)
250  * converting to upper-case in the process.
251  * File name can be terminated by '\0', '\\' or '/'.
252  * 'buffer' must be at least 13 characters long.
253  */
254 static void DOSFS_ToDosDTAFormat( LPCSTR name, LPSTR buffer )
255 {
256     char *p;
257
258     memcpy( buffer, name, 8 );
259     for (p = buffer + 8; (p > buffer) && (p[-1] == ' '); p--);
260     *p++ = '.';
261     memcpy( p, name + 8, 3 );
262     for (p += 3; p[-1] == ' '; p--);
263     if (p[-1] == '.') p--;
264     *p = '\0';
265 }
266
267
268 /***********************************************************************
269  *           DOSFS_MatchShort
270  *
271  * Check a DOS file name against a mask (both in FCB format).
272  */
273 static int DOSFS_MatchShort( const char *mask, const char *name )
274 {
275     int i;
276     for (i = 11; i > 0; i--, mask++, name++)
277         if ((*mask != '?') && (*mask != *name)) return 0;
278     return 1;
279 }
280
281
282 /***********************************************************************
283  *           DOSFS_MatchLong
284  *
285  * Check a long file name against a mask.
286  */
287 static int DOSFS_MatchLong( const char *mask, const char *name,
288                             int case_sensitive )
289 {
290     if (!strcmp( mask, "*.*" )) return 1;
291     while (*name && *mask)
292     {
293         if (*mask == '*')
294         {
295             mask++;
296             while (*mask == '*') mask++;  /* Skip consecutive '*' */
297             if (!*mask) return 1;
298             if (case_sensitive) while (*name && (*name != *mask)) name++;
299             else while (*name && (toupper(*name) != toupper(*mask))) name++;
300             if (!*name) break;
301         }
302         else if (*mask != '?')
303         {
304             if (case_sensitive)
305             {
306                 if (*mask != *name) return 0;
307             }
308             else if (toupper(*mask) != toupper(*name)) return 0;
309         }
310         mask++;
311         name++;
312     }
313     if (*mask == '.') mask++;  /* Ignore trailing '.' in mask */
314     return (!*name && !*mask);
315 }
316
317
318 /***********************************************************************
319  *           DOSFS_OpenDir
320  */
321 static DOS_DIR *DOSFS_OpenDir( LPCSTR path )
322 {
323     DOS_DIR *dir = HeapAlloc( SystemHeap, 0, sizeof(*dir) );
324     if (!dir)
325     {
326         SetLastError( ERROR_NOT_ENOUGH_MEMORY );
327         return NULL;
328     }
329
330     /* Treat empty path as root directory. This simplifies path split into
331        directory and mask in several other places */
332     if (!*path) path = "/";
333
334 #ifdef VFAT_IOCTL_READDIR_BOTH
335
336     /* Check if the VFAT ioctl is supported on this directory */
337
338     if ((dir->fd = open( path, O_RDONLY )) != -1)
339     {
340         if (ioctl( dir->fd, VFAT_IOCTL_READDIR_BOTH, (long)dir->dirent ) == -1)
341         {
342             close( dir->fd );
343             dir->fd = -1;
344         }
345         else
346         {
347             /* Set the file pointer back at the start of the directory */
348             lseek( dir->fd, 0, SEEK_SET );
349             dir->dir = NULL;
350             return dir;
351         }
352     }
353 #endif  /* VFAT_IOCTL_READDIR_BOTH */
354
355     /* Now use the standard opendir/readdir interface */
356
357     if (!(dir->dir = opendir( path )))
358     {
359         HeapFree( SystemHeap, 0, dir );
360         return NULL;
361     }
362     return dir;
363 }
364
365
366 /***********************************************************************
367  *           DOSFS_CloseDir
368  */
369 static void DOSFS_CloseDir( DOS_DIR *dir )
370 {
371 #ifdef VFAT_IOCTL_READDIR_BOTH
372     if (dir->fd != -1) close( dir->fd );
373 #endif  /* VFAT_IOCTL_READDIR_BOTH */
374     if (dir->dir) closedir( dir->dir );
375     HeapFree( SystemHeap, 0, dir );
376 }
377
378
379 /***********************************************************************
380  *           DOSFS_ReadDir
381  */
382 static BOOL DOSFS_ReadDir( DOS_DIR *dir, LPCSTR *long_name,
383                              LPCSTR *short_name )
384 {
385     struct dirent *dirent;
386
387 #ifdef VFAT_IOCTL_READDIR_BOTH
388     if (dir->fd != -1)
389     {
390         if (ioctl( dir->fd, VFAT_IOCTL_READDIR_BOTH, (long)dir->dirent ) != -1) {
391             if (!dir->dirent[0].d_reclen) return FALSE;
392             if (!DOSFS_ToDosFCBFormat( dir->dirent[0].d_name, dir->short_name ))
393                 dir->short_name[0] = '\0';
394             *short_name = dir->short_name;
395             if (dir->dirent[1].d_name[0]) *long_name = dir->dirent[1].d_name;
396             else *long_name = dir->dirent[0].d_name;
397             return TRUE;
398         }
399     }
400 #endif  /* VFAT_IOCTL_READDIR_BOTH */
401
402     if (!(dirent = readdir( dir->dir ))) return FALSE;
403     *long_name  = dirent->d_name;
404     *short_name = NULL;
405     return TRUE;
406 }
407
408
409 /***********************************************************************
410  *           DOSFS_Hash
411  *
412  * Transform a Unix file name into a hashed DOS name. If the name is a valid
413  * DOS name, it is converted to upper-case; otherwise it is replaced by a
414  * hashed version that fits in 8.3 format.
415  * File name can be terminated by '\0', '\\' or '/'.
416  * 'buffer' must be at least 13 characters long.
417  */
418 static void DOSFS_Hash( LPCSTR name, LPSTR buffer, BOOL dir_format,
419                         BOOL ignore_case )
420 {
421     static const char invalid_chars[] = INVALID_DOS_CHARS "~.";
422     static const char hash_chars[32] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345";
423
424     const char *p, *ext;
425     char *dst;
426     unsigned short hash;
427     int i;
428
429     if (dir_format) strcpy( buffer, "           " );
430
431     if (DOSFS_ValidDOSName( name, ignore_case ))
432     {
433         /* Check for '.' and '..' */
434         if (*name == '.')
435         {
436             buffer[0] = '.';
437             if (!dir_format) buffer[1] = buffer[2] = '\0';
438             if (name[1] == '.') buffer[1] = '.';
439             return;
440         }
441
442         /* Simply copy the name, converting to uppercase */
443
444         for (dst = buffer; !IS_END_OF_NAME(*name) && (*name != '.'); name++)
445             *dst++ = toupper(*name);
446         if (*name == '.')
447         {
448             if (dir_format) dst = buffer + 8;
449             else *dst++ = '.';
450             for (name++; !IS_END_OF_NAME(*name); name++)
451                 *dst++ = toupper(*name);
452         }
453         if (!dir_format) *dst = '\0';
454         return;
455     }
456
457     /* Compute the hash code of the file name */
458     /* If you know something about hash functions, feel free to */
459     /* insert a better algorithm here... */
460     if (ignore_case)
461     {
462         for (p = name, hash = 0xbeef; !IS_END_OF_NAME(p[1]); p++)
463             hash = (hash<<3) ^ (hash>>5) ^ tolower(*p) ^ (tolower(p[1]) << 8);
464         hash = (hash<<3) ^ (hash>>5) ^ tolower(*p); /* Last character*/
465     }
466     else
467     {
468         for (p = name, hash = 0xbeef; !IS_END_OF_NAME(p[1]); p++)
469             hash = (hash << 3) ^ (hash >> 5) ^ *p ^ (p[1] << 8);
470         hash = (hash << 3) ^ (hash >> 5) ^ *p;  /* Last character */
471     }
472
473     /* Find last dot for start of the extension */
474     for (p = name+1, ext = NULL; !IS_END_OF_NAME(*p); p++)
475         if (*p == '.') ext = p;
476     if (ext && IS_END_OF_NAME(ext[1]))
477         ext = NULL;  /* Empty extension ignored */
478
479     /* Copy first 4 chars, replacing invalid chars with '_' */
480     for (i = 4, p = name, dst = buffer; i > 0; i--, p++)
481     {
482         if (IS_END_OF_NAME(*p) || (p == ext)) break;
483         *dst++ = strchr( invalid_chars, *p ) ? '_' : toupper(*p);
484     }
485     /* Pad to 5 chars with '~' */
486     while (i-- >= 0) *dst++ = '~';
487
488     /* Insert hash code converted to 3 ASCII chars */
489     *dst++ = hash_chars[(hash >> 10) & 0x1f];
490     *dst++ = hash_chars[(hash >> 5) & 0x1f];
491     *dst++ = hash_chars[hash & 0x1f];
492
493     /* Copy the first 3 chars of the extension (if any) */
494     if (ext)
495     {
496         if (!dir_format) *dst++ = '.';
497         for (i = 3, ext++; (i > 0) && !IS_END_OF_NAME(*ext); i--, ext++)
498             *dst++ = strchr( invalid_chars, *ext ) ? '_' : toupper(*ext);
499     }
500     if (!dir_format) *dst = '\0';
501 }
502
503
504 /***********************************************************************
505  *           DOSFS_FindUnixName
506  *
507  * Find the Unix file name in a given directory that corresponds to
508  * a file name (either in Unix or DOS format).
509  * File name can be terminated by '\0', '\\' or '/'.
510  * Return TRUE if OK, FALSE if no file name matches.
511  *
512  * 'long_buf' must be at least 'long_len' characters long. If the long name
513  * turns out to be larger than that, the function returns FALSE.
514  * 'short_buf' must be at least 13 characters long.
515  */
516 BOOL DOSFS_FindUnixName( LPCSTR path, LPCSTR name, LPSTR long_buf,
517                            INT long_len, LPSTR short_buf, BOOL ignore_case)
518 {
519     DOS_DIR *dir;
520     LPCSTR long_name, short_name;
521     char dos_name[12], tmp_buf[13];
522     BOOL ret;
523
524     const char *p = strchr( name, '/' );
525     int len = p ? (int)(p - name) : strlen(name);
526     if ((p = strchr( name, '\\' ))) len = MIN( (int)(p - name), len );
527     /* Ignore trailing dots */
528     while (len > 1 && name[len-1] == '.') len--;
529     if (long_len < len + 1) return FALSE;
530
531     TRACE_(dosfs)("%s,%s\n", path, name );
532
533     if (!DOSFS_ToDosFCBFormat( name, dos_name )) dos_name[0] = '\0';
534
535     if (!(dir = DOSFS_OpenDir( path )))
536     {
537         WARN_(dosfs)("(%s,%s): can't open dir: %s\n",
538                        path, name, strerror(errno) );
539         return FALSE;
540     }
541
542     while ((ret = DOSFS_ReadDir( dir, &long_name, &short_name )))
543     {
544         /* Check against Unix name */
545         if (len == strlen(long_name))
546         {
547             if (!ignore_case)
548             {
549                 if (!strncmp( long_name, name, len )) break;
550             }
551             else
552             {
553                 if (!lstrncmpiA( long_name, name, len )) break;
554             }
555         }
556         if (dos_name[0])
557         {
558             /* Check against hashed DOS name */
559             if (!short_name)
560             {
561                 DOSFS_Hash( long_name, tmp_buf, TRUE, ignore_case );
562                 short_name = tmp_buf;
563             }
564             if (!strcmp( dos_name, short_name )) break;
565         }
566     }
567     if (ret)
568     {
569         if (long_buf) strcpy( long_buf, long_name );
570         if (short_buf)
571         {
572             if (short_name)
573                 DOSFS_ToDosDTAFormat( short_name, short_buf );
574             else
575                 DOSFS_Hash( long_name, short_buf, FALSE, ignore_case );
576         }
577         TRACE_(dosfs)("(%s,%s) -> %s (%s)\n",
578                      path, name, long_name, short_buf ? short_buf : "***");
579     }
580     else
581         WARN_(dosfs)("'%s' not found in '%s'\n", name, path);
582     DOSFS_CloseDir( dir );
583     return ret;
584 }
585
586
587 /***********************************************************************
588  *           DOSFS_GetDevice
589  *
590  * Check if a DOS file name represents a DOS device and return the device.
591  */
592 const DOS_DEVICE *DOSFS_GetDevice( const char *name )
593 {
594     int i;
595     const char *p;
596
597     if (!name) return NULL; /* if FILE_DupUnixHandle was used */
598     if (name[0] && (name[1] == ':')) name += 2;
599     if ((p = strrchr( name, '/' ))) name = p + 1;
600     if ((p = strrchr( name, '\\' ))) name = p + 1;
601     for (i = 0; i < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0]); i++)
602     {
603         const char *dev = DOSFS_Devices[i].name;
604         if (!lstrncmpiA( dev, name, strlen(dev) ))
605         {
606             p = name + strlen( dev );
607             if (!*p || (*p == '.')) return &DOSFS_Devices[i];
608         }
609     }
610     return NULL;
611 }
612
613
614 /***********************************************************************
615  *           DOSFS_GetDeviceByHandle
616  */
617 const DOS_DEVICE *DOSFS_GetDeviceByHandle( HFILE hFile )
618 {
619     struct get_file_info_request *req = get_req_buffer();
620
621     req->handle = hFile;
622     if (!server_call( REQ_GET_FILE_INFO ) && (req->type == FILE_TYPE_UNKNOWN))
623     {
624         if ((req->attr >= 0) &&
625             (req->attr < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0])))
626             return &DOSFS_Devices[req->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 || ((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 (result)
1140     {
1141         if (unicode)
1142             lstrcpynAtoW( (LPWSTR)result, buffer, len );
1143         else
1144             lstrcpynA( result, buffer, len );
1145     }
1146
1147     TRACE_(dosfs)("returning '%s'\n", buffer );
1148
1149     /* If the lpBuffer buffer is too small, the return value is the 
1150     size of the buffer, in characters, required to hold the path. */
1151
1152     ret = strlen(buffer);
1153
1154     if (ret >= len )
1155         SetLastError( ERROR_INSUFFICIENT_BUFFER );
1156
1157     return ret;
1158 }
1159
1160
1161 /***********************************************************************
1162  *           GetFullPathName32A   (KERNEL32.272)
1163  * NOTES
1164  *   if the path closed with '\', *lastpart is 0 
1165  */
1166 DWORD WINAPI GetFullPathNameA( LPCSTR name, DWORD len, LPSTR buffer,
1167                                  LPSTR *lastpart )
1168 {
1169     DWORD ret = DOSFS_DoGetFullPathName( name, len, buffer, FALSE );
1170     if (ret && buffer && lastpart)
1171     {
1172         LPSTR p = buffer + strlen(buffer);
1173
1174         if (*p != '\\')
1175         {
1176           while ((p > buffer + 2) && (*p != '\\')) p--;
1177           *lastpart = p + 1;
1178         }
1179         else *lastpart = NULL;
1180     }
1181     return ret;
1182 }
1183
1184
1185 /***********************************************************************
1186  *           GetFullPathName32W   (KERNEL32.273)
1187  */
1188 DWORD WINAPI GetFullPathNameW( LPCWSTR name, DWORD len, LPWSTR buffer,
1189                                  LPWSTR *lastpart )
1190 {
1191     LPSTR nameA = HEAP_strdupWtoA( GetProcessHeap(), 0, name );
1192     DWORD ret = DOSFS_DoGetFullPathName( nameA, len, (LPSTR)buffer, TRUE );
1193     HeapFree( GetProcessHeap(), 0, nameA );
1194     if (ret && buffer && lastpart)
1195     {
1196         LPWSTR p = buffer + lstrlenW(buffer);
1197         if (*p != (WCHAR)'\\')
1198         {
1199             while ((p > buffer + 2) && (*p != (WCHAR)'\\')) p--;
1200             *lastpart = p + 1;
1201         }
1202         else *lastpart = NULL;  
1203     }
1204     return ret;
1205 }
1206
1207 /***********************************************************************
1208  *           DOSFS_FindNextEx
1209  */
1210 static int DOSFS_FindNextEx( FIND_FIRST_INFO *info, WIN32_FIND_DATAA *entry )
1211 {
1212     BYTE attr = info->attr | FA_UNUSED | FA_ARCHIVE | FA_RDONLY;
1213     UINT flags = DRIVE_GetFlags( info->drive );
1214     char *p, buffer[MAX_PATHNAME_LEN];
1215     const char *drive_path;
1216     int drive_root;
1217     LPCSTR long_name, short_name;
1218     BY_HANDLE_FILE_INFORMATION fileinfo;
1219     char dos_name[13];
1220
1221     if ((info->attr & ~(FA_UNUSED | FA_ARCHIVE | FA_RDONLY)) == FA_LABEL)
1222     {
1223         if (info->cur_pos) return 0;
1224         entry->dwFileAttributes  = FILE_ATTRIBUTE_LABEL;
1225         DOSFS_UnixTimeToFileTime( (time_t)0, &entry->ftCreationTime, 0 );
1226         DOSFS_UnixTimeToFileTime( (time_t)0, &entry->ftLastAccessTime, 0 );
1227         DOSFS_UnixTimeToFileTime( (time_t)0, &entry->ftLastWriteTime, 0 );
1228         entry->nFileSizeHigh     = 0;
1229         entry->nFileSizeLow      = 0;
1230         entry->dwReserved0       = 0;
1231         entry->dwReserved1       = 0;
1232         DOSFS_ToDosDTAFormat( DRIVE_GetLabel( info->drive ), entry->cFileName );
1233         strcpy( entry->cAlternateFileName, entry->cFileName ); 
1234         info->cur_pos++;
1235         return 1;
1236     }
1237
1238     drive_path = info->path + strlen(DRIVE_GetRoot( info->drive ));
1239     while ((*drive_path == '/') || (*drive_path == '\\')) drive_path++;
1240     drive_root = !*drive_path;
1241
1242     lstrcpynA( buffer, info->path, sizeof(buffer) - 1 );
1243     strcat( buffer, "/" );
1244     p = buffer + strlen(buffer);
1245
1246     while (DOSFS_ReadDir( info->dir, &long_name, &short_name ))
1247     {
1248         info->cur_pos++;
1249
1250         /* Don't return '.' and '..' in the root of the drive */
1251         if (drive_root && (long_name[0] == '.') &&
1252             (!long_name[1] || ((long_name[1] == '.') && !long_name[2])))
1253             continue;
1254
1255         /* Check the long mask */
1256
1257         if (info->long_mask)
1258         {
1259             if (!DOSFS_MatchLong( info->long_mask, long_name,
1260                                   flags & DRIVE_CASE_SENSITIVE )) continue;
1261         }
1262
1263         /* Check the short mask */
1264
1265         if (info->short_mask)
1266         {
1267             if (!short_name)
1268             {
1269                 DOSFS_Hash( long_name, dos_name, TRUE,
1270                             !(flags & DRIVE_CASE_SENSITIVE) );
1271                 short_name = dos_name;
1272             }
1273             if (!DOSFS_MatchShort( info->short_mask, short_name )) continue;
1274         }
1275
1276         /* Check the file attributes */
1277
1278         lstrcpynA( p, long_name, sizeof(buffer) - (int)(p - buffer) );
1279         if (!FILE_Stat( buffer, &fileinfo ))
1280         {
1281             WARN_(dosfs)("can't stat %s\n", buffer);
1282             continue;
1283         }
1284         if (fileinfo.dwFileAttributes & ~attr) continue;
1285
1286         /* We now have a matching entry; fill the result and return */
1287
1288         entry->dwFileAttributes = fileinfo.dwFileAttributes;
1289         entry->ftCreationTime   = fileinfo.ftCreationTime;
1290         entry->ftLastAccessTime = fileinfo.ftLastAccessTime;
1291         entry->ftLastWriteTime  = fileinfo.ftLastWriteTime;
1292         entry->nFileSizeHigh    = fileinfo.nFileSizeHigh;
1293         entry->nFileSizeLow     = fileinfo.nFileSizeLow;
1294
1295         if (short_name)
1296             DOSFS_ToDosDTAFormat( short_name, entry->cAlternateFileName );
1297         else
1298             DOSFS_Hash( long_name, entry->cAlternateFileName, FALSE,
1299                         !(flags & DRIVE_CASE_SENSITIVE) );
1300
1301         lstrcpynA( entry->cFileName, long_name, sizeof(entry->cFileName) );
1302         if (!(flags & DRIVE_CASE_PRESERVING)) CharLowerA( entry->cFileName );
1303         TRACE_(dosfs)("returning %s (%s) %02lx %ld\n",
1304                        entry->cFileName, entry->cAlternateFileName,
1305                        entry->dwFileAttributes, entry->nFileSizeLow );
1306         return 1;
1307     }
1308     return 0;  /* End of directory */
1309 }
1310
1311 /***********************************************************************
1312  *           DOSFS_FindNext
1313  *
1314  * Find the next matching file. Return the number of entries read to find
1315  * the matching one, or 0 if no more entries.
1316  * 'short_mask' is the 8.3 mask (in FCB format), 'long_mask' is the long
1317  * file name mask. Either or both can be NULL.
1318  *
1319  * NOTE: This is supposed to be only called by the int21 emulation
1320  *       routines. Thus, we should own the Win16Mutex anyway.
1321  *       Nevertheless, we explicitly enter it to ensure the static
1322  *       directory cache is protected.
1323  */
1324 int DOSFS_FindNext( const char *path, const char *short_mask,
1325                     const char *long_mask, int drive, BYTE attr,
1326                     int skip, WIN32_FIND_DATAA *entry )
1327 {
1328     static FIND_FIRST_INFO info = { NULL };
1329     LPCSTR short_name, long_name;
1330     int count;
1331
1332     SYSLEVEL_EnterWin16Lock();
1333
1334     /* Check the cached directory */
1335     if (!(info.dir && info.path == path && info.short_mask == short_mask
1336                    && info.long_mask == long_mask && info.drive == drive
1337                    && info.attr == attr && info.cur_pos <= skip))
1338     {  
1339         /* Not in the cache, open it anew */
1340         if (info.dir) DOSFS_CloseDir( info.dir );
1341
1342         info.path = (LPSTR)path;
1343         info.long_mask = (LPSTR)long_mask;
1344         info.short_mask = (LPSTR)short_mask;
1345         info.attr = attr;
1346         info.drive = drive;
1347         info.cur_pos = 0;
1348         info.dir = DOSFS_OpenDir( info.path );
1349     }
1350
1351     /* Skip to desired position */
1352     while (info.cur_pos < skip)
1353         if (info.dir && DOSFS_ReadDir( info.dir, &long_name, &short_name ))
1354             info.cur_pos++;
1355         else
1356             break;
1357
1358     if (info.dir && info.cur_pos == skip && DOSFS_FindNextEx( &info, entry ))
1359         count = info.cur_pos - skip;
1360     else
1361         count = 0;
1362
1363     if (!count)
1364     {
1365         if (info.dir) DOSFS_CloseDir( info.dir );
1366         memset( &info, '\0', sizeof(info) );
1367     }
1368
1369     SYSLEVEL_LeaveWin16Lock();
1370
1371     return count;
1372 }
1373
1374
1375
1376 /*************************************************************************
1377  *           FindFirstFile16   (KERNEL.413)
1378  */
1379 HANDLE16 WINAPI FindFirstFile16( LPCSTR path, WIN32_FIND_DATAA *data )
1380 {
1381     DOS_FULL_NAME full_name;
1382     HGLOBAL16 handle;
1383     FIND_FIRST_INFO *info;
1384
1385     data->dwReserved0 = data->dwReserved1 = 0x0;
1386     if (!path) return 0;
1387     if (!DOSFS_GetFullName( path, FALSE, &full_name ))
1388         return INVALID_HANDLE_VALUE16;
1389     if (!(handle = GlobalAlloc16( GMEM_MOVEABLE, sizeof(FIND_FIRST_INFO) )))
1390         return INVALID_HANDLE_VALUE16;
1391     info = (FIND_FIRST_INFO *)GlobalLock16( handle );
1392     info->path = HEAP_strdupA( SystemHeap, 0, full_name.long_name );
1393     info->long_mask = strrchr( info->path, '/' );
1394     *(info->long_mask++) = '\0';
1395     info->short_mask = NULL;
1396     info->attr = 0xff;
1397     if (path[0] && (path[1] == ':')) info->drive = toupper(*path) - 'A';
1398     else info->drive = DRIVE_GetCurrentDrive();
1399     info->cur_pos = 0;
1400
1401     info->dir = DOSFS_OpenDir( info->path );
1402
1403     GlobalUnlock16( handle );
1404     if (!FindNextFile16( handle, data ))
1405     {
1406         FindClose16( handle );
1407         SetLastError( ERROR_NO_MORE_FILES );
1408         return INVALID_HANDLE_VALUE16;
1409     }
1410     return handle;
1411 }
1412
1413
1414 /*************************************************************************
1415  *           FindFirstFile32A   (KERNEL32.123)
1416  */
1417 HANDLE WINAPI FindFirstFileA( LPCSTR path, WIN32_FIND_DATAA *data )
1418 {
1419     HANDLE handle = FindFirstFile16( path, data );
1420     if (handle == INVALID_HANDLE_VALUE16) return INVALID_HANDLE_VALUE;
1421     return handle;
1422 }
1423
1424
1425 /*************************************************************************
1426  *           FindFirstFile32W   (KERNEL32.124)
1427  */
1428 HANDLE WINAPI FindFirstFileW( LPCWSTR path, WIN32_FIND_DATAW *data )
1429 {
1430     WIN32_FIND_DATAA dataA;
1431     LPSTR pathA = HEAP_strdupWtoA( GetProcessHeap(), 0, path );
1432     HANDLE handle = FindFirstFileA( pathA, &dataA );
1433     HeapFree( GetProcessHeap(), 0, pathA );
1434     if (handle != INVALID_HANDLE_VALUE)
1435     {
1436         data->dwFileAttributes = dataA.dwFileAttributes;
1437         data->ftCreationTime   = dataA.ftCreationTime;
1438         data->ftLastAccessTime = dataA.ftLastAccessTime;
1439         data->ftLastWriteTime  = dataA.ftLastWriteTime;
1440         data->nFileSizeHigh    = dataA.nFileSizeHigh;
1441         data->nFileSizeLow     = dataA.nFileSizeLow;
1442         lstrcpyAtoW( data->cFileName, dataA.cFileName );
1443         lstrcpyAtoW( data->cAlternateFileName, dataA.cAlternateFileName );
1444     }
1445     return handle;
1446 }
1447
1448
1449 /*************************************************************************
1450  *           FindNextFile16   (KERNEL.414)
1451  */
1452 BOOL16 WINAPI FindNextFile16( HANDLE16 handle, WIN32_FIND_DATAA *data )
1453 {
1454     FIND_FIRST_INFO *info;
1455
1456     if (!(info = (FIND_FIRST_INFO *)GlobalLock16( handle )))
1457     {
1458         SetLastError( ERROR_INVALID_HANDLE );
1459         return FALSE;
1460     }
1461     GlobalUnlock16( handle );
1462     if (!info->path || !info->dir)
1463     {
1464         SetLastError( ERROR_NO_MORE_FILES );
1465         return FALSE;
1466     }
1467     if (!DOSFS_FindNextEx( info, data ))
1468     {
1469         DOSFS_CloseDir( info->dir ); info->dir = NULL;
1470         HeapFree( SystemHeap, 0, info->path );
1471         info->path = info->long_mask = NULL;
1472         SetLastError( ERROR_NO_MORE_FILES );
1473         return FALSE;
1474     }
1475     return TRUE;
1476 }
1477
1478
1479 /*************************************************************************
1480  *           FindNextFile32A   (KERNEL32.126)
1481  */
1482 BOOL WINAPI FindNextFileA( HANDLE handle, WIN32_FIND_DATAA *data )
1483 {
1484     return FindNextFile16( handle, data );
1485 }
1486
1487
1488 /*************************************************************************
1489  *           FindNextFile32W   (KERNEL32.127)
1490  */
1491 BOOL WINAPI FindNextFileW( HANDLE handle, WIN32_FIND_DATAW *data )
1492 {
1493     WIN32_FIND_DATAA dataA;
1494     if (!FindNextFileA( handle, &dataA )) return FALSE;
1495     data->dwFileAttributes = dataA.dwFileAttributes;
1496     data->ftCreationTime   = dataA.ftCreationTime;
1497     data->ftLastAccessTime = dataA.ftLastAccessTime;
1498     data->ftLastWriteTime  = dataA.ftLastWriteTime;
1499     data->nFileSizeHigh    = dataA.nFileSizeHigh;
1500     data->nFileSizeLow     = dataA.nFileSizeLow;
1501     lstrcpyAtoW( data->cFileName, dataA.cFileName );
1502     lstrcpyAtoW( data->cAlternateFileName, dataA.cAlternateFileName );
1503     return TRUE;
1504 }
1505
1506
1507 /*************************************************************************
1508  *           FindClose16   (KERNEL.415)
1509  */
1510 BOOL16 WINAPI FindClose16( HANDLE16 handle )
1511 {
1512     FIND_FIRST_INFO *info;
1513
1514     if ((handle == INVALID_HANDLE_VALUE16) ||
1515         !(info = (FIND_FIRST_INFO *)GlobalLock16( handle )))
1516     {
1517         SetLastError( ERROR_INVALID_HANDLE );
1518         return FALSE;
1519     }
1520     if (info->dir) DOSFS_CloseDir( info->dir );
1521     if (info->path) HeapFree( SystemHeap, 0, info->path );
1522     GlobalUnlock16( handle );
1523     GlobalFree16( handle );
1524     return TRUE;
1525 }
1526
1527
1528 /*************************************************************************
1529  *           FindClose32   (KERNEL32.119)
1530  */
1531 BOOL WINAPI FindClose( HANDLE handle )
1532 {
1533     return FindClose16( (HANDLE16)handle );
1534 }
1535
1536
1537 /***********************************************************************
1538  *           DOSFS_UnixTimeToFileTime
1539  *
1540  * Convert a Unix time to FILETIME format.
1541  * The FILETIME structure is a 64-bit value representing the number of
1542  * 100-nanosecond intervals since January 1, 1601, 0:00.
1543  * 'remainder' is the nonnegative number of 100-ns intervals
1544  * corresponding to the time fraction smaller than 1 second that
1545  * couldn't be stored in the time_t value.
1546  */
1547 void DOSFS_UnixTimeToFileTime( time_t unix_time, FILETIME *filetime,
1548                                DWORD remainder )
1549 {
1550     /* NOTES:
1551
1552        CONSTANTS: 
1553        The time difference between 1 January 1601, 00:00:00 and
1554        1 January 1970, 00:00:00 is 369 years, plus the leap years
1555        from 1604 to 1968, excluding 1700, 1800, 1900.
1556        This makes (1968 - 1600) / 4 - 3 = 89 leap days, and a total
1557        of 134774 days.
1558
1559        Any day in that period had 24 * 60 * 60 = 86400 seconds.
1560
1561        The time difference is 134774 * 86400 * 10000000, which can be written
1562        116444736000000000
1563        27111902 * 2^32 + 3577643008
1564        413 * 2^48 + 45534 * 2^32 + 54590 * 2^16 + 32768
1565
1566        If you find that these constants are buggy, please change them in all
1567        instances in both conversion functions.
1568
1569        VERSIONS:
1570        There are two versions, one of them uses long long variables and
1571        is presumably faster but not ISO C. The other one uses standard C
1572        data types and operations but relies on the assumption that negative
1573        numbers are stored as 2's complement (-1 is 0xffff....). If this
1574        assumption is violated, dates before 1970 will not convert correctly.
1575        This should however work on any reasonable architecture where WINE
1576        will run.
1577
1578        DETAILS:
1579        
1580        Take care not to remove the casts. I have tested these functions
1581        (in both versions) for a lot of numbers. I would be interested in
1582        results on other compilers than GCC.
1583
1584        The operations have been designed to account for the possibility
1585        of 64-bit time_t in future UNICES. Even the versions without
1586        internal long long numbers will work if time_t only is 64 bit.
1587        A 32-bit shift, which was necessary for that operation, turned out
1588        not to work correctly in GCC, besides giving the warning. So I
1589        used a double 16-bit shift instead. Numbers are in the ISO version
1590        represented by three limbs, the most significant with 32 bit, the
1591        other two with 16 bit each.
1592
1593        As the modulo-operator % is not well-defined for negative numbers,
1594        negative divisors have been avoided in DOSFS_FileTimeToUnixTime.
1595
1596        There might be quicker ways to do this in C. Certainly so in
1597        assembler.
1598
1599        Claus Fischer, fischer@iue.tuwien.ac.at
1600        */
1601
1602 #if SIZEOF_LONG_LONG >= 8
1603 #  define USE_LONG_LONG 1
1604 #else
1605 #  define USE_LONG_LONG 0
1606 #endif
1607
1608 #if USE_LONG_LONG               /* gcc supports long long type */
1609
1610     long long int t = unix_time;
1611     t *= 10000000;
1612     t += 116444736000000000LL;
1613     t += remainder;
1614     filetime->dwLowDateTime  = (UINT)t;
1615     filetime->dwHighDateTime = (UINT)(t >> 32);
1616
1617 #else  /* ISO version */
1618
1619     UINT a0;                    /* 16 bit, low    bits */
1620     UINT a1;                    /* 16 bit, medium bits */
1621     UINT a2;                    /* 32 bit, high   bits */
1622
1623     /* Copy the unix time to a2/a1/a0 */
1624     a0 =  unix_time & 0xffff;
1625     a1 = (unix_time >> 16) & 0xffff;
1626     /* This is obsolete if unix_time is only 32 bits, but it does not hurt.
1627        Do not replace this by >> 32, it gives a compiler warning and it does
1628        not work. */
1629     a2 = (unix_time >= 0 ? (unix_time >> 16) >> 16 :
1630           ~((~unix_time >> 16) >> 16));
1631
1632     /* Multiply a by 10000000 (a = a2/a1/a0)
1633        Split the factor into 10000 * 1000 which are both less than 0xffff. */
1634     a0 *= 10000;
1635     a1 = a1 * 10000 + (a0 >> 16);
1636     a2 = a2 * 10000 + (a1 >> 16);
1637     a0 &= 0xffff;
1638     a1 &= 0xffff;
1639
1640     a0 *= 1000;
1641     a1 = a1 * 1000 + (a0 >> 16);
1642     a2 = a2 * 1000 + (a1 >> 16);
1643     a0 &= 0xffff;
1644     a1 &= 0xffff;
1645
1646     /* Add the time difference and the remainder */
1647     a0 += 32768 + (remainder & 0xffff);
1648     a1 += 54590 + (remainder >> 16   ) + (a0 >> 16);
1649     a2 += 27111902                     + (a1 >> 16);
1650     a0 &= 0xffff;
1651     a1 &= 0xffff;
1652
1653     /* Set filetime */
1654     filetime->dwLowDateTime  = (a1 << 16) + a0;
1655     filetime->dwHighDateTime = a2;
1656 #endif
1657 }
1658
1659
1660 /***********************************************************************
1661  *           DOSFS_FileTimeToUnixTime
1662  *
1663  * Convert a FILETIME format to Unix time.
1664  * If not NULL, 'remainder' contains the fractional part of the filetime,
1665  * in the range of [0..9999999] (even if time_t is negative).
1666  */
1667 time_t DOSFS_FileTimeToUnixTime( const FILETIME *filetime, DWORD *remainder )
1668 {
1669     /* Read the comment in the function DOSFS_UnixTimeToFileTime. */
1670 #if USE_LONG_LONG
1671
1672     long long int t = filetime->dwHighDateTime;
1673     t <<= 32;
1674     t += (UINT)filetime->dwLowDateTime;
1675     t -= 116444736000000000LL;
1676     if (t < 0)
1677     {
1678         if (remainder) *remainder = 9999999 - (-t - 1) % 10000000;
1679         return -1 - ((-t - 1) / 10000000);
1680     }
1681     else
1682     {
1683         if (remainder) *remainder = t % 10000000;
1684         return t / 10000000;
1685     }
1686
1687 #else  /* ISO version */
1688
1689     UINT a0;                    /* 16 bit, low    bits */
1690     UINT a1;                    /* 16 bit, medium bits */
1691     UINT a2;                    /* 32 bit, high   bits */
1692     UINT r;                     /* remainder of division */
1693     unsigned int carry;         /* carry bit for subtraction */
1694     int negative;               /* whether a represents a negative value */
1695
1696     /* Copy the time values to a2/a1/a0 */
1697     a2 =  (UINT)filetime->dwHighDateTime;
1698     a1 = ((UINT)filetime->dwLowDateTime ) >> 16;
1699     a0 = ((UINT)filetime->dwLowDateTime ) & 0xffff;
1700
1701     /* Subtract the time difference */
1702     if (a0 >= 32768           ) a0 -=             32768        , carry = 0;
1703     else                        a0 += (1 << 16) - 32768        , carry = 1;
1704
1705     if (a1 >= 54590    + carry) a1 -=             54590 + carry, carry = 0;
1706     else                        a1 += (1 << 16) - 54590 - carry, carry = 1;
1707
1708     a2 -= 27111902 + carry;
1709     
1710     /* If a is negative, replace a by (-1-a) */
1711     negative = (a2 >= ((UINT)1) << 31);
1712     if (negative)
1713     {
1714         /* Set a to -a - 1 (a is a2/a1/a0) */
1715         a0 = 0xffff - a0;
1716         a1 = 0xffff - a1;
1717         a2 = ~a2;
1718     }
1719
1720     /* Divide a by 10000000 (a = a2/a1/a0), put the rest into r.
1721        Split the divisor into 10000 * 1000 which are both less than 0xffff. */
1722     a1 += (a2 % 10000) << 16;
1723     a2 /=       10000;
1724     a0 += (a1 % 10000) << 16;
1725     a1 /=       10000;
1726     r   =  a0 % 10000;
1727     a0 /=       10000;
1728
1729     a1 += (a2 % 1000) << 16;
1730     a2 /=       1000;
1731     a0 += (a1 % 1000) << 16;
1732     a1 /=       1000;
1733     r  += (a0 % 1000) * 10000;
1734     a0 /=       1000;
1735
1736     /* If a was negative, replace a by (-1-a) and r by (9999999 - r) */
1737     if (negative)
1738     {
1739         /* Set a to -a - 1 (a is a2/a1/a0) */
1740         a0 = 0xffff - a0;
1741         a1 = 0xffff - a1;
1742         a2 = ~a2;
1743
1744         r  = 9999999 - r;
1745     }
1746
1747     if (remainder) *remainder = r;
1748
1749     /* Do not replace this by << 32, it gives a compiler warning and it does
1750        not work. */
1751     return ((((time_t)a2) << 16) << 16) + (a1 << 16) + a0;
1752 #endif
1753 }
1754
1755
1756 /***********************************************************************
1757  *           DosDateTimeToFileTime   (KERNEL32.76)
1758  */
1759 BOOL WINAPI DosDateTimeToFileTime( WORD fatdate, WORD fattime, LPFILETIME ft)
1760 {
1761     struct tm newtm;
1762
1763     newtm.tm_sec  = (fattime & 0x1f) * 2;
1764     newtm.tm_min  = (fattime >> 5) & 0x3f;
1765     newtm.tm_hour = (fattime >> 11);
1766     newtm.tm_mday = (fatdate & 0x1f);
1767     newtm.tm_mon  = ((fatdate >> 5) & 0x0f) - 1;
1768     newtm.tm_year = (fatdate >> 9) + 80;
1769     DOSFS_UnixTimeToFileTime( mktime( &newtm ), ft, 0 );
1770     return TRUE;
1771 }
1772
1773
1774 /***********************************************************************
1775  *           FileTimeToDosDateTime   (KERNEL32.111)
1776  */
1777 BOOL WINAPI FileTimeToDosDateTime( const FILETIME *ft, LPWORD fatdate,
1778                                      LPWORD fattime )
1779 {
1780     time_t unixtime = DOSFS_FileTimeToUnixTime( ft, NULL );
1781     struct tm *tm = localtime( &unixtime );
1782     if (fattime)
1783         *fattime = (tm->tm_hour << 11) + (tm->tm_min << 5) + (tm->tm_sec / 2);
1784     if (fatdate)
1785         *fatdate = ((tm->tm_year - 80) << 9) + ((tm->tm_mon + 1) << 5)
1786                    + tm->tm_mday;
1787     return TRUE;
1788 }
1789
1790
1791 /***********************************************************************
1792  *           LocalFileTimeToFileTime   (KERNEL32.373)
1793  */
1794 BOOL WINAPI LocalFileTimeToFileTime( const FILETIME *localft,
1795                                        LPFILETIME utcft )
1796 {
1797     struct tm *xtm;
1798     DWORD remainder;
1799
1800     /* convert from local to UTC. Perhaps not correct. FIXME */
1801     time_t unixtime = DOSFS_FileTimeToUnixTime( localft, &remainder );
1802     xtm = gmtime( &unixtime );
1803     DOSFS_UnixTimeToFileTime( mktime(xtm), utcft, remainder );
1804     return TRUE; 
1805 }
1806
1807
1808 /***********************************************************************
1809  *           FileTimeToLocalFileTime   (KERNEL32.112)
1810  */
1811 BOOL WINAPI FileTimeToLocalFileTime( const FILETIME *utcft,
1812                                        LPFILETIME localft )
1813 {
1814     DWORD remainder;
1815     /* convert from UTC to local. Perhaps not correct. FIXME */
1816     time_t unixtime = DOSFS_FileTimeToUnixTime( utcft, &remainder );
1817 #ifdef HAVE_TIMEGM
1818     struct tm *xtm = localtime( &unixtime );
1819     time_t localtime;
1820
1821     localtime = timegm(xtm);
1822     DOSFS_UnixTimeToFileTime( localtime, localft, remainder );
1823
1824 #else
1825     struct tm *xtm,*gtm;
1826     time_t time1,time2;
1827
1828     xtm = localtime( &unixtime );
1829     gtm = gmtime( &unixtime );
1830     time1 = mktime(xtm);
1831     time2 = mktime(gtm);
1832     DOSFS_UnixTimeToFileTime( 2*time1-time2, localft, remainder );
1833 #endif
1834     return TRUE; 
1835 }
1836
1837
1838 /***********************************************************************
1839  *           FileTimeToSystemTime   (KERNEL32.113)
1840  */
1841 BOOL WINAPI FileTimeToSystemTime( const FILETIME *ft, LPSYSTEMTIME syst )
1842 {
1843     struct tm *xtm;
1844     DWORD remainder;
1845     time_t xtime = DOSFS_FileTimeToUnixTime( ft, &remainder );
1846     xtm = gmtime(&xtime);
1847     syst->wYear         = xtm->tm_year+1900;
1848     syst->wMonth        = xtm->tm_mon + 1;
1849     syst->wDayOfWeek    = xtm->tm_wday;
1850     syst->wDay          = xtm->tm_mday;
1851     syst->wHour         = xtm->tm_hour;
1852     syst->wMinute       = xtm->tm_min;
1853     syst->wSecond       = xtm->tm_sec;
1854     syst->wMilliseconds = remainder / 10000;
1855     return TRUE; 
1856 }
1857
1858 /***********************************************************************
1859  *           QueryDosDeviceA   (KERNEL32.413)
1860  *
1861  * returns array of strings terminated by \0, terminated by \0
1862  */
1863 DWORD WINAPI QueryDosDeviceA(LPCSTR devname,LPSTR target,DWORD bufsize)
1864 {
1865     LPSTR s;
1866     char  buffer[200];
1867
1868     TRACE_(dosfs)("(%s,...)\n",devname?devname:"<null>");
1869     if (!devname) {
1870         /* return known MSDOS devices */
1871         strcpy(buffer,"CON COM1 COM2 LPT1 NUL ");
1872         while ((s=strchr(buffer,' ')))
1873                 *s='\0';
1874
1875         lstrcpynA(target,buffer,bufsize);
1876         return strlen(buffer);
1877     }
1878     strcpy(buffer,"\\DEV\\");
1879     strcat(buffer,devname);
1880     if ((s=strchr(buffer,':'))) *s='\0';
1881     lstrcpynA(target,buffer,bufsize);
1882     return strlen(buffer);
1883 }
1884
1885
1886 /***********************************************************************
1887  *           QueryDosDeviceW   (KERNEL32.414)
1888  *
1889  * returns array of strings terminated by \0, terminated by \0
1890  */
1891 DWORD WINAPI QueryDosDeviceW(LPCWSTR devname,LPWSTR target,DWORD bufsize)
1892 {
1893     LPSTR devnameA = devname?HEAP_strdupWtoA(GetProcessHeap(),0,devname):NULL;
1894     LPSTR targetA = (LPSTR)HEAP_xalloc(GetProcessHeap(),0,bufsize);
1895     DWORD ret = QueryDosDeviceA(devnameA,targetA,bufsize);
1896
1897     lstrcpynAtoW(target,targetA,bufsize);
1898     if (devnameA) HeapFree(GetProcessHeap(),0,devnameA);
1899     if (targetA) HeapFree(GetProcessHeap(),0,targetA);
1900     return ret;
1901 }
1902
1903
1904 /***********************************************************************
1905  *           SystemTimeToFileTime   (KERNEL32.526)
1906  */
1907 BOOL WINAPI SystemTimeToFileTime( const SYSTEMTIME *syst, LPFILETIME ft )
1908 {
1909 #ifdef HAVE_TIMEGM
1910     struct tm xtm;
1911     time_t utctime;
1912 #else
1913     struct tm xtm,*local_tm,*utc_tm;
1914     time_t localtim,utctime;
1915 #endif
1916
1917     xtm.tm_year = syst->wYear-1900;
1918     xtm.tm_mon  = syst->wMonth - 1;
1919     xtm.tm_wday = syst->wDayOfWeek;
1920     xtm.tm_mday = syst->wDay;
1921     xtm.tm_hour = syst->wHour;
1922     xtm.tm_min  = syst->wMinute;
1923     xtm.tm_sec  = syst->wSecond; /* this is UTC */
1924     xtm.tm_isdst = -1;
1925 #ifdef HAVE_TIMEGM
1926     utctime = timegm(&xtm);
1927     DOSFS_UnixTimeToFileTime( utctime, ft, 
1928                               syst->wMilliseconds * 10000 );
1929 #else
1930     localtim = mktime(&xtm);    /* now we've got local time */
1931     local_tm = localtime(&localtim);
1932     utc_tm = gmtime(&localtim);
1933     utctime = mktime(utc_tm);
1934     DOSFS_UnixTimeToFileTime( 2*localtim -utctime, ft, 
1935                               syst->wMilliseconds * 10000 );
1936 #endif
1937     return TRUE; 
1938 }
1939
1940 /***********************************************************************
1941  *           DefineDosDeviceA       (KERNEL32.182)
1942  */
1943 BOOL WINAPI DefineDosDeviceA(DWORD flags,LPCSTR devname,LPCSTR targetpath) {
1944         FIXME_(dosfs)("(0x%08lx,%s,%s),stub!\n",flags,devname,targetpath);
1945         SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
1946         return FALSE;
1947 }