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