Release 970616
[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 <sys/types.h>
9 #include <ctype.h>
10 #include <dirent.h>
11 #include <fcntl.h>
12 #include <string.h>
13 #include <stdlib.h>
14 #include <stdio.h>
15 #include <sys/stat.h>
16 #include <sys/ioctl.h>
17 #include <time.h>
18 #include <unistd.h>
19 #if defined(__svr4__) || defined(_SCO_DS)
20 #include <sys/statfs.h>
21 #endif
22
23 #include "windows.h"
24 #include "winerror.h"
25 #include "drive.h"
26 #include "file.h"
27 #include "heap.h"
28 #include "msdos.h"
29 #include "stddebug.h"
30 #include "debug.h"
31
32 /* Define the VFAT ioctl to get both short and long file names */
33 /* FIXME: is it possible to get this to work on other systems? */
34 #ifdef linux
35 #define VFAT_IOCTL_READDIR_BOTH  _IOR('r', 1, long)
36 /* We want the real kernel dirent structure, not the libc one */
37 typedef struct
38 {
39     long d_ino;
40     long d_off;
41     unsigned short d_reclen;
42     char d_name[256];
43 } KERNEL_DIRENT;
44
45 #else   /* linux */
46 #undef VFAT_IOCTL_READDIR_BOTH  /* just in case... */
47 #endif  /* linux */
48
49 /* Chars we don't want to see in DOS file names */
50 #define INVALID_DOS_CHARS  "*?<>|\"+=,;[] \345"
51
52 static const char *DOSFS_Devices[][2] =
53 {
54     { "CON",  "" },
55     { "PRN",  "" },
56     { "NUL",  "/dev/null" },
57     { "AUX",  "" },
58     { "LPT1", "" },
59     { "LPT2", "" },
60     { "LPT3", "" },
61     { "LPT4", "" },
62     { "COM1", "" },
63     { "COM2", "" },
64     { "COM3", "" },
65     { "COM4", "" }
66 };
67
68
69 #define GET_DRIVE(path) \
70     (((path)[1] == ':') ? toupper((path)[0]) - 'A' : DOSFS_CurDrive)
71
72     /* DOS extended error status */
73 WORD DOS_ExtendedError;
74 BYTE DOS_ErrorClass;
75 BYTE DOS_ErrorAction;
76 BYTE DOS_ErrorLocus;
77
78 /* Info structure for FindFirstFile handle */
79 typedef struct
80 {
81     LPSTR path;
82     LPSTR mask;
83     int   drive;
84     int   skip;
85 } FIND_FIRST_INFO;
86
87
88 /* Directory info for DOSFS_ReadDir */
89 typedef struct
90 {
91     DIR           *dir;
92 #ifdef VFAT_IOCTL_READDIR_BOTH
93     int            fd;
94     char           short_name[12];
95     KERNEL_DIRENT  dirent[2];
96 #endif
97 } DOS_DIR;
98
99
100 /***********************************************************************
101  *           DOSFS_ValidDOSName
102  *
103  * Return 1 if Unix file 'name' is also a valid MS-DOS name
104  * (i.e. contains only valid DOS chars, lower-case only, fits in 8.3 format).
105  * File name can be terminated by '\0', '\\' or '/'.
106  */
107 static int DOSFS_ValidDOSName( const char *name, int ignore_case )
108 {
109     static const char invalid_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" INVALID_DOS_CHARS;
110     const char *p = name;
111     const char *invalid = ignore_case ? (invalid_chars + 26) : invalid_chars;
112     int len = 0;
113
114     if (*p == '.')
115     {
116         /* Check for "." and ".." */
117         p++;
118         if (*p == '.') p++;
119         /* All other names beginning with '.' are invalid */
120         return (IS_END_OF_NAME(*p));
121     }
122     while (!IS_END_OF_NAME(*p))
123     {
124         if (strchr( invalid, *p )) return 0;  /* Invalid char */
125         if (*p == '.') break;  /* Start of the extension */
126         if (++len > 8) return 0;  /* Name too long */
127         p++;
128     }
129     if (*p != '.') return 1;  /* End of name */
130     p++;
131     if (IS_END_OF_NAME(*p)) return 0;  /* Empty extension not allowed */
132     len = 0;
133     while (!IS_END_OF_NAME(*p))
134     {
135         if (strchr( invalid, *p )) return 0;  /* Invalid char */
136         if (*p == '.') return 0;  /* Second extension not allowed */
137         if (++len > 3) return 0;  /* Extension too long */
138         p++;
139     }
140     return 1;
141 }
142
143
144 /***********************************************************************
145  *           DOSFS_ToDosFCBFormat
146  *
147  * Convert a file name to DOS FCB format (8+3 chars, padded with blanks),
148  * expanding wild cards and converting to upper-case in the process.
149  * File name can be terminated by '\0', '\\' or '/'.
150  * Return FALSE if the name is not a valid DOS name.
151  * 'buffer' must be at least 12 characters long.
152  */
153 BOOL32 DOSFS_ToDosFCBFormat( LPCSTR name, LPSTR buffer )
154 {
155     static const char invalid_chars[] = INVALID_DOS_CHARS;
156     const char *p = name;
157     int i;
158
159     /* Check for "." and ".." */
160     if (*p == '.')
161     {
162         p++;
163         strcpy( buffer, ".          " );
164         if (*p == '.')
165         {
166             buffer[1] = '.';
167             p++;
168         }
169         return (!*p || (*p == '/') || (*p == '\\'));
170     }
171
172     for (i = 0; i < 8; i++)
173     {
174         switch(*p)
175         {
176         case '\0':
177         case '\\':
178         case '/':
179         case '.':
180             buffer[i] = ' ';
181             break;
182         case '?':
183             p++;
184             /* fall through */
185         case '*':
186             buffer[i] = '?';
187             break;
188         default:
189             if (strchr( invalid_chars, *p )) return FALSE;
190             buffer[i] = toupper(*p);
191             p++;
192             break;
193         }
194     }
195
196     if (*p == '*')
197     {
198         /* Skip all chars after wildcard up to first dot */
199         while (*p && (*p != '/') && (*p != '\\') && (*p != '.')) p++;
200     }
201     else
202     {
203         /* Check if name too long */
204         if (*p && (*p != '/') && (*p != '\\') && (*p != '.')) return FALSE;
205     }
206     if (*p == '.') p++;  /* Skip dot */
207
208     for (i = 8; i < 11; i++)
209     {
210         switch(*p)
211         {
212         case '\0':
213         case '\\':
214         case '/':
215             buffer[i] = ' ';
216             break;
217         case '.':
218             return FALSE;  /* Second extension not allowed */
219         case '?':
220             p++;
221             /* fall through */
222         case '*':
223             buffer[i] = '?';
224             break;
225         default:
226             if (strchr( invalid_chars, *p )) return FALSE;
227             buffer[i] = toupper(*p);
228             p++;
229             break;
230         }
231     }
232     buffer[11] = '\0';
233     return TRUE;
234 }
235
236
237 /***********************************************************************
238  *           DOSFS_ToDosDTAFormat
239  *
240  * Convert a file name from FCB to DTA format (name.ext, null-terminated)
241  * converting to upper-case in the process.
242  * File name can be terminated by '\0', '\\' or '/'.
243  * 'buffer' must be at least 13 characters long.
244  */
245 static void DOSFS_ToDosDTAFormat( LPCSTR name, LPSTR buffer )
246 {
247     char *p;
248
249     memcpy( buffer, name, 8 );
250     for (p = buffer + 8; (p > buffer) && (p[-1] == ' '); p--);
251     *p++ = '.';
252     memcpy( p, name + 8, 3 );
253     for (p += 3; p[-1] == ' '; p--);
254     if (p[-1] == '.') p--;
255     *p = '\0';
256 }
257
258
259 /***********************************************************************
260  *           DOSFS_MatchShort
261  *
262  * Check a DOS file name against a mask (both in FCB format).
263  */
264 static int DOSFS_MatchShort( const char *mask, const char *name )
265 {
266     int i;
267     for (i = 11; i > 0; i--, mask++, name++)
268         if ((*mask != '?') && (*mask != *name)) return 0;
269     return 1;
270 }
271
272
273 /***********************************************************************
274  *           DOSFS_MatchLong
275  *
276  * Check a long file name against a mask.
277  */
278 static int DOSFS_MatchLong( const char *mask, const char *name,
279                             int case_sensitive )
280 {
281     if (!strcmp( mask, "*.*" )) return 1;
282     while (*name && *mask)
283     {
284         if (*mask == '*')
285         {
286             mask++;
287             while (*mask == '*') mask++;  /* Skip consecutive '*' */
288             if (!*mask) return 1;
289             if (case_sensitive) while (*name && (*name != *mask)) name++;
290             else while (*name && (toupper(*name) != toupper(*mask))) name++;
291             if (!*name) return 0;
292         }
293         else if (*mask != '?')
294         {
295             if (case_sensitive)
296             {
297                 if (*mask != *name) return 0;
298             }
299             else if (toupper(*mask) != toupper(*name)) return 0;
300         }
301         mask++;
302         name++;
303     }
304     return (!*name && !*mask);
305 }
306
307
308 /***********************************************************************
309  *           DOSFS_OpenDir
310  */
311 static DOS_DIR *DOSFS_OpenDir( LPCSTR path )
312 {
313     DOS_DIR *dir = HeapAlloc( SystemHeap, 0, sizeof(*dir) );
314     if (!dir)
315     {
316         DOS_ERROR( ER_OutOfMemory, EC_OutOfResource, SA_Abort, EL_Memory );
317         return NULL;
318     }
319
320 #ifdef VFAT_IOCTL_READDIR_BOTH
321
322     /* Check if the VFAT ioctl is supported on this directory */
323
324     if ((dir->fd = open( path, O_RDONLY )) != -1)
325     {
326         if (ioctl( dir->fd, VFAT_IOCTL_READDIR_BOTH, (long)dir->dirent ) == -1)
327         {
328             close( dir->fd );
329             dir->fd = -1;
330         }
331         else
332         {
333             /* Set the file pointer back at the start of the directory */
334             lseek( dir->fd, 0, SEEK_SET );
335             dir->dir = NULL;
336             return dir;
337         }
338     }
339 #endif  /* VFAT_IOCTL_READDIR_BOTH */
340
341     /* Now use the standard opendir/readdir interface */
342
343     if (!(dir->dir = opendir( path )))
344     {
345         HeapFree( SystemHeap, 0, dir );
346         return NULL;
347     }
348     return dir;
349 }
350
351
352 /***********************************************************************
353  *           DOSFS_CloseDir
354  */
355 static void DOSFS_CloseDir( DOS_DIR *dir )
356 {
357 #ifdef VFAT_IOCTL_READDIR_BOTH
358     if (dir->fd != -1) close( dir->fd );
359 #endif  /* VFAT_IOCTL_READDIR_BOTH */
360     if (dir->dir) closedir( dir->dir );
361     HeapFree( SystemHeap, 0, dir );
362 }
363
364
365 /***********************************************************************
366  *           DOSFS_ReadDir
367  */
368 static BOOL32 DOSFS_ReadDir( DOS_DIR *dir, LPCSTR *long_name,
369                              LPCSTR *short_name )
370 {
371     struct dirent *dirent;
372
373 #ifdef VFAT_IOCTL_READDIR_BOTH
374     if (dir->fd != -1)
375     {
376         if (ioctl( dir->fd, VFAT_IOCTL_READDIR_BOTH, (long)dir->dirent ) == -1)
377             return FALSE;
378         if (!dir->dirent[0].d_reclen) return FALSE;
379         if (!DOSFS_ToDosFCBFormat( dir->dirent[0].d_name, dir->short_name ))
380             dir->short_name[0] = '\0';
381         *short_name = dir->short_name;
382         if (dir->dirent[1].d_name[0]) *long_name = dir->dirent[1].d_name;
383         else *long_name = dir->dirent[0].d_name;
384         return TRUE;
385     }
386 #endif  /* VFAT_IOCTL_READDIR_BOTH */
387
388     if (!(dirent = readdir( dir->dir ))) return FALSE;
389     *long_name  = dirent->d_name;
390     *short_name = NULL;
391     return TRUE;
392 }
393
394
395 /***********************************************************************
396  *           DOSFS_Hash
397  *
398  * Transform a Unix file name into a hashed DOS name. If the name is a valid
399  * DOS name, it is converted to upper-case; otherwise it is replaced by a
400  * hashed version that fits in 8.3 format.
401  * File name can be terminated by '\0', '\\' or '/'.
402  * 'buffer' must be at least 13 characters long.
403  */
404 static void DOSFS_Hash( LPCSTR name, LPSTR buffer, BOOL32 dir_format,
405                         BOOL32 ignore_case )
406 {
407     static const char invalid_chars[] = INVALID_DOS_CHARS "~.";
408     static const char hash_chars[32] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345";
409
410     const char *p, *ext;
411     char *dst;
412     unsigned short hash;
413     int i;
414
415     if (dir_format) strcpy( buffer, "           " );
416
417     if (DOSFS_ValidDOSName( name, ignore_case ))
418     {
419         /* Check for '.' and '..' */
420         if (*name == '.')
421         {
422             buffer[0] = '.';
423             if (!dir_format) buffer[1] = buffer[2] = '\0';
424             if (name[1] == '.') buffer[1] = '.';
425             return;
426         }
427
428         /* Simply copy the name, converting to uppercase */
429
430         for (dst = buffer; !IS_END_OF_NAME(*name) && (*name != '.'); name++)
431             *dst++ = toupper(*name);
432         if (*name == '.')
433         {
434             if (dir_format) dst = buffer + 8;
435             else *dst++ = '.';
436             for (name++; !IS_END_OF_NAME(*name); name++)
437                 *dst++ = toupper(*name);
438         }
439         if (!dir_format) *dst = '\0';
440         return;
441     }
442
443     /* Compute the hash code of the file name */
444     /* If you know something about hash functions, feel free to */
445     /* insert a better algorithm here... */
446     if (ignore_case)
447     {
448         for (p = name, hash = 0xbeef; !IS_END_OF_NAME(p[1]); p++)
449             hash = (hash<<3) ^ (hash>>5) ^ tolower(*p) ^ (tolower(p[1]) << 8);
450         hash = (hash<<3) ^ (hash>>5) ^ tolower(*p); /* Last character*/
451     }
452     else
453     {
454         for (p = name, hash = 0xbeef; !IS_END_OF_NAME(p[1]); p++)
455             hash = (hash << 3) ^ (hash >> 5) ^ *p ^ (p[1] << 8);
456         hash = (hash << 3) ^ (hash >> 5) ^ *p;  /* Last character */
457     }
458
459     /* Find last dot for start of the extension */
460     for (p = name+1, ext = NULL; !IS_END_OF_NAME(*p); p++)
461         if (*p == '.') ext = p;
462     if (ext && IS_END_OF_NAME(ext[1]))
463         ext = NULL;  /* Empty extension ignored */
464
465     /* Copy first 4 chars, replacing invalid chars with '_' */
466     for (i = 4, p = name, dst = buffer; i > 0; i--, p++)
467     {
468         if (IS_END_OF_NAME(*p) || (p == ext)) break;
469         *dst++ = strchr( invalid_chars, *p ) ? '_' : toupper(*p);
470     }
471     /* Pad to 5 chars with '~' */
472     while (i-- >= 0) *dst++ = '~';
473
474     /* Insert hash code converted to 3 ASCII chars */
475     *dst++ = hash_chars[(hash >> 10) & 0x1f];
476     *dst++ = hash_chars[(hash >> 5) & 0x1f];
477     *dst++ = hash_chars[hash & 0x1f];
478
479     /* Copy the first 3 chars of the extension (if any) */
480     if (ext)
481     {
482         if (!dir_format) *dst++ = '.';
483         for (i = 3, ext++; (i > 0) && !IS_END_OF_NAME(*ext); i--, ext++)
484             *dst++ = strchr( invalid_chars, *ext ) ? '_' : toupper(*ext);
485     }
486     if (!dir_format) *dst = '\0';
487 }
488
489
490 /***********************************************************************
491  *           DOSFS_FindUnixName
492  *
493  * Find the Unix file name in a given directory that corresponds to
494  * a file name (either in Unix or DOS format).
495  * File name can be terminated by '\0', '\\' or '/'.
496  * Return TRUE if OK, FALSE if no file name matches.
497  *
498  * 'long_buf' must be at least 'long_len' characters long. If the long name
499  * turns out to be larger than that, the function returns FALSE.
500  * 'short_buf' must be at least 13 characters long.
501  */
502 BOOL32 DOSFS_FindUnixName( LPCSTR path, LPCSTR name, LPSTR long_buf,
503                            INT32 long_len, LPSTR short_buf, BOOL32 ignore_case)
504 {
505     DOS_DIR *dir;
506     LPCSTR long_name, short_name;
507     char dos_name[12], tmp_buf[13];
508     BOOL32 ret;
509
510     const char *p = strchr( name, '/' );
511     int len = p ? (int)(p - name) : strlen(name);
512     if ((p = strchr( name, '\\' ))) len = MIN( (int)(p - name), len );
513     if (long_len < len + 1) return FALSE;
514
515     dprintf_dosfs( stddeb, "DOSFS_FindUnixName: %s,%s\n", path, name );
516
517     if (!DOSFS_ToDosFCBFormat( name, dos_name )) dos_name[0] = '\0';
518
519     if (!(dir = DOSFS_OpenDir( path )))
520     {
521         dprintf_dosfs( stddeb, "DOSFS_FindUnixName(%s,%s): can't open dir\n",
522                        path, name );
523         return FALSE;
524     }
525
526     while ((ret = DOSFS_ReadDir( dir, &long_name, &short_name )))
527     {
528         /* Check against Unix name */
529         if (len == strlen(long_name))
530         {
531             if (!ignore_case)
532             {
533                 if (!lstrncmp32A( long_name, name, len )) break;
534             }
535             else
536             {
537                 if (!lstrncmpi32A( long_name, name, len )) break;
538             }
539         }
540         if (dos_name[0])
541         {
542             /* Check against hashed DOS name */
543             if (!short_name)
544             {
545                 DOSFS_Hash( long_name, tmp_buf, TRUE, ignore_case );
546                 short_name = tmp_buf;
547             }
548             if (!strcmp( dos_name, short_name )) break;
549         }
550     }
551     if (ret)
552     {
553         if (long_buf) strcpy( long_buf, long_name );
554         if (short_buf)
555         {
556             if (short_name)
557                 DOSFS_ToDosDTAFormat( short_name, short_buf );
558             else
559                 DOSFS_Hash( long_name, short_buf, FALSE, ignore_case );
560         }
561         dprintf_dosfs( stddeb, "DOSFS_FindUnixName(%s,%s) -> %s (%s)\n",
562                        path, name, long_name, short_buf ? short_buf : "***");
563     }
564     else
565         dprintf_dosfs(stddeb,"DOSFS_FindUnixName(%s,%s) -> ** Not found **\n",
566                       path, name );
567     DOSFS_CloseDir( dir );
568     return ret;
569 }
570
571
572 /***********************************************************************
573  *           DOSFS_IsDevice
574  *
575  * Check if a DOS file name represents a DOS device. Returns the name
576  * of the associated Unix device, or NULL if not found.
577  */
578 const char *DOSFS_IsDevice( const char *name )
579 {
580     int i;
581     const char *p;
582
583     if (name[0] && (name[1] == ':')) name += 2;
584     if ((p = strrchr( name, '/' ))) name = p + 1;
585     if ((p = strrchr( name, '\\' ))) name = p + 1;
586     for (i = 0; i < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0]); i++)
587     {
588         const char *dev = DOSFS_Devices[i][0];
589         if (!lstrncmpi32A( dev, name, strlen(dev) ))
590         {
591             p = name + strlen( dev );
592             if (!*p || (*p == '.')) return DOSFS_Devices[i][1];
593         }
594     }
595     return NULL;
596 }
597
598 /***********************************************************************
599  *           DOSFS_GetPathDrive
600  *
601  * Get the drive specified by a given path name (DOS or Unix format).
602  */
603 static int DOSFS_GetPathDrive( const char **name )
604 {
605     int drive;
606     const char *p = *name;
607
608     if (*p && (p[1] == ':'))
609     {
610         drive = toupper(*p) - 'A';
611         *name += 2;
612     }
613     else if (*p == '/') /* Absolute Unix path? */
614     {
615         if ((drive = DRIVE_FindDriveRoot( name )) == -1)
616         {
617             fprintf( stderr, "Warning: %s not accessible from a DOS drive\n",
618                      *name );
619             /* Assume it really was a DOS name */
620             drive = DRIVE_GetCurrentDrive();            
621         }
622     }
623     else drive = DRIVE_GetCurrentDrive();
624
625     if (!DRIVE_IsValid(drive))
626     {
627         DOS_ERROR( ER_InvalidDrive, EC_MediaError, SA_Abort, EL_Disk );
628         return -1;
629     }
630     return drive;
631 }
632
633
634 /***********************************************************************
635  *           DOSFS_GetFullName
636  *
637  * Convert a file name (DOS or mixed DOS/Unix format) to a valid
638  * Unix name / short DOS name pair.
639  * Return FALSE if one of the path components does not exist. The last path
640  * component is only checked if 'check_last' is non-zero.
641  * The buffers pointed to by 'long_buf' and 'short_buf' must be
642  * at least MAX_PATHNAME_LEN long.
643  */
644 BOOL32 DOSFS_GetFullName( LPCSTR name, BOOL32 check_last, DOS_FULL_NAME *full )
645 {
646     BOOL32 found;
647     UINT32 flags;
648     char *p_l, *p_s, *root;
649
650     dprintf_dosfs( stddeb, "DOSFS_GetFullName: %s (last=%d)\n",
651                    name, check_last );
652
653     if ((full->drive = DOSFS_GetPathDrive( &name )) == -1) return FALSE;
654     flags = DRIVE_GetFlags( full->drive );
655
656     lstrcpyn32A( full->long_name, DRIVE_GetRoot( full->drive ),
657                  sizeof(full->long_name) );
658     if (full->long_name[1]) root = full->long_name + strlen(full->long_name);
659     else root = full->long_name;  /* root directory */
660
661     strcpy( full->short_name, "A:\\" );
662     full->short_name[0] += full->drive;
663
664     if ((*name == '\\') || (*name == '/'))  /* Absolute path */
665     {
666         while ((*name == '\\') || (*name == '/')) name++;
667     }
668     else  /* Relative path */
669     {
670         lstrcpyn32A( root + 1, DRIVE_GetUnixCwd( full->drive ),
671                      sizeof(full->long_name) - (root - full->long_name) - 1 );
672         if (root[1]) *root = '/';
673         lstrcpyn32A( full->short_name + 3, DRIVE_GetDosCwd( full->drive ),
674                      sizeof(full->short_name) - 3 );
675     }
676
677     p_l = full->long_name[1] ? full->long_name + strlen(full->long_name)
678                              : full->long_name;
679     p_s = full->short_name[3] ? full->short_name + strlen(full->short_name)
680                               : full->short_name + 2;
681     found = TRUE;
682
683     while (*name && found)
684     {
685         /* Check for '.' and '..' */
686
687         if (*name == '.')
688         {
689             if (IS_END_OF_NAME(name[1]))
690             {
691                 name++;
692                 while ((*name == '\\') || (*name == '/')) name++;
693                 continue;
694             }
695             else if ((name[1] == '.') && IS_END_OF_NAME(name[2]))
696             {
697                 name += 2;
698                 while ((*name == '\\') || (*name == '/')) name++;
699                 while ((p_l > root) && (*p_l != '/')) p_l--;
700                 while ((p_s > full->short_name + 2) && (*p_s != '\\')) p_s--;
701                 *p_l = *p_s = '\0';  /* Remove trailing separator */
702                 continue;
703             }
704         }
705
706         /* Make sure buffers are large enough */
707
708         if ((p_s >= full->short_name + sizeof(full->short_name) - 14) ||
709             (p_l >= full->long_name + sizeof(full->long_name) - 1))
710         {
711             DOS_ERROR( ER_PathNotFound, EC_NotFound, SA_Abort, EL_Disk );
712             return FALSE;
713         }
714
715         /* Get the long and short name matching the file name */
716
717         if ((found = DOSFS_FindUnixName( full->long_name, name, p_l + 1,
718                          sizeof(full->long_name) - (p_l - full->long_name) - 1,
719                          p_s + 1, !(flags & DRIVE_CASE_SENSITIVE) )))
720         {
721             *p_l++ = '/';
722             p_l   += strlen(p_l);
723             *p_s++ = '\\';
724             p_s   += strlen(p_s);
725             while (!IS_END_OF_NAME(*name)) name++;
726         }
727         else if (!check_last)
728         {
729             *p_l++ = '/';
730             *p_s++ = '\\';
731             while (!IS_END_OF_NAME(*name) &&
732                    (p_s < full->short_name + sizeof(full->short_name) - 1) &&
733                    (p_l < full->long_name + sizeof(full->long_name) - 1))
734             {
735                 *p_l++ = *p_s++ = tolower(*name);
736                 name++;
737             }
738             *p_l = *p_s = '\0';
739         }
740         while ((*name == '\\') || (*name == '/')) name++;
741     }
742
743     if (!found)
744     {
745         if (check_last)
746         {
747             DOS_ERROR( ER_FileNotFound, EC_NotFound, SA_Abort, EL_Disk );
748             return FALSE;
749         }
750         if (*name)  /* Not last */
751         {
752             DOS_ERROR( ER_PathNotFound, EC_NotFound, SA_Abort, EL_Disk );
753             return FALSE;
754         }
755     }
756     if (!full->long_name[0]) strcpy( full->long_name, "/" );
757     if (!full->short_name[2]) strcpy( full->short_name + 2, "\\" );
758     dprintf_dosfs( stddeb, "DOSFS_GetFullName: returning %s = %s\n",
759                    full->long_name, full->short_name );
760     return TRUE;
761 }
762
763
764 /***********************************************************************
765  *           GetShortPathName32A   (KERNEL32.271)
766  */
767 DWORD GetShortPathName32A( LPCSTR longpath, LPSTR shortpath, DWORD shortlen )
768 {
769     DOS_FULL_NAME full_name;
770
771     /* FIXME: is it correct to always return a fully qualified short path? */
772     if (!DOSFS_GetFullName( longpath, TRUE, &full_name )) return 0;
773     lstrcpyn32A( shortpath, full_name.short_name, shortlen );
774     return strlen( full_name.short_name );
775 }
776
777
778 /***********************************************************************
779  *           GetShortPathName32W   (KERNEL32.272)
780  */
781 DWORD GetShortPathName32W( LPCWSTR longpath, LPWSTR shortpath, DWORD shortlen )
782 {
783     DOS_FULL_NAME full_name;
784     DWORD ret = 0;
785     LPSTR longpathA = HEAP_strdupWtoA( GetProcessHeap(), 0, longpath );
786
787     /* FIXME: is it correct to always return a fully qualified short path? */
788     if (DOSFS_GetFullName( longpathA, TRUE, &full_name ))
789     {
790         ret = strlen( full_name.short_name );
791         lstrcpynAtoW( shortpath, full_name.short_name, shortlen );
792     }
793     HeapFree( GetProcessHeap(), 0, longpathA );
794     return ret;
795 }
796
797
798 /***********************************************************************
799  *           DOSFS_DoGetFullPathName
800  *
801  * Implementation of GetFullPathName32A/W.
802  */
803 static DWORD DOSFS_DoGetFullPathName( LPCSTR name, DWORD len, LPSTR result,
804                                       BOOL32 unicode )
805 {
806     char buffer[MAX_PATHNAME_LEN];
807     int drive;
808     char *p;
809
810     dprintf_dosfs( stddeb, "GetFullPathName: converting %s\n", name );
811
812     if (!name || !result) return 0;
813
814     if ((drive = DOSFS_GetPathDrive( &name )) == -1) return 0;
815     p = buffer;
816     *p++ = 'A' + drive;
817     *p++ = ':';
818     if (IS_END_OF_NAME(*name))  /* Absolute path */
819     {
820         while ((*name == '\\') || (*name == '/')) name++;
821     }
822     else  /* Relative path */
823     {
824         *p++ = '\\';
825         lstrcpyn32A( p, DRIVE_GetDosCwd(drive), sizeof(buffer) - 3 );
826         if (*p) p += strlen(p); else p--;
827     }
828     *p = '\0';
829
830     while (*name)
831     {
832         if (*name == '.')
833         {
834             if (IS_END_OF_NAME(name[1]))
835             {
836                 name++;
837                 while ((*name == '\\') || (*name == '/')) name++;
838                 continue;
839             }
840             else if ((name[1] == '.') && IS_END_OF_NAME(name[2]))
841             {
842                 name += 2;
843                 while ((*name == '\\') || (*name == '/')) name++;
844                 while ((p > buffer + 2) && (*p != '\\')) p--;
845                 *p = '\0';  /* Remove trailing separator */
846                 continue;
847             }
848         }
849         if (p >= buffer + sizeof(buffer) - 1)
850         {
851             DOS_ERROR( ER_PathNotFound, EC_NotFound, SA_Abort, EL_Disk );
852             return 0;
853         }
854         *p++ = '\\';
855         while (!IS_END_OF_NAME(*name) && (p < buffer + sizeof(buffer) - 1))
856             *p++ = *name++;
857         *p = '\0';
858         while ((*name == '\\') || (*name == '/')) name++;
859     }
860
861     if (!buffer[2])
862     {
863         buffer[2] = '\\';
864         buffer[3] = '\0';
865     }
866     if (!(DRIVE_GetFlags(drive) & DRIVE_CASE_PRESERVING))
867         CharUpper32A( buffer );
868
869     if (unicode) lstrcpynAtoW( (LPWSTR)result, buffer, len );
870     else lstrcpyn32A( result, buffer, len );
871
872     dprintf_dosfs( stddeb, "GetFullPathName: returning %s\n", buffer );
873     return strlen(buffer);
874 }
875
876
877 /***********************************************************************
878  *           GetFullPathName32A   (KERNEL32.272)
879  */
880 DWORD GetFullPathName32A(LPCSTR name, DWORD len, LPSTR buffer, LPSTR *lastpart)
881 {
882     DWORD ret = DOSFS_DoGetFullPathName( name, len, buffer, FALSE );
883     if (ret && lastpart)
884     {
885         LPSTR p = buffer + strlen(buffer);
886         while ((p > buffer + 2) && (*p != '\\')) p--;
887         *lastpart = p + 1;
888     }
889     return ret;
890 }
891
892
893 /***********************************************************************
894  *           GetFullPathName32W   (KERNEL32.273)
895  */
896 DWORD GetFullPathName32W( LPCWSTR name, DWORD len, LPWSTR buffer,
897                           LPWSTR *lastpart )
898 {
899     LPSTR nameA = HEAP_strdupWtoA( GetProcessHeap(), 0, name );
900     DWORD ret = DOSFS_DoGetFullPathName( nameA, len, (LPSTR)buffer, TRUE );
901     HeapFree( GetProcessHeap(), 0, nameA );
902     if (ret && lastpart)
903     {
904         LPWSTR p = buffer + lstrlen32W(buffer);
905         while ((p > buffer + 2) && (*p != '\\')) p--;
906         *lastpart = p + 1;
907     }
908     return ret;
909 }
910
911
912 /***********************************************************************
913  *           DOSFS_FindNext
914  *
915  * Find the next matching file. Return the number of entries read to find
916  * the matching one, or 0 if no more entries.
917  * 'short_mask' is the 8.3 mask (in FCB format), 'long_mask' is the long
918  * file name mask. Either or both can be NULL.
919  */
920 int DOSFS_FindNext( const char *path, const char *short_mask,
921                     const char *long_mask, int drive, BYTE attr,
922                     int skip, WIN32_FIND_DATA32A *entry )
923 {
924     static DOS_DIR *dir = NULL;
925     int count = 0;
926     static char buffer[MAX_PATHNAME_LEN];
927     static int cur_pos = 0;
928     static int drive_root = 0;
929     char *p;
930     char dos_name[13];
931     LPCSTR long_name, short_name;
932     UINT32 flags;
933     BY_HANDLE_FILE_INFORMATION info;
934
935     if ((attr & ~(FA_UNUSED | FA_ARCHIVE | FA_RDONLY)) == FA_LABEL)
936     {
937         if (skip) return 0;
938         entry->dwFileAttributes  = FILE_ATTRIBUTE_LABEL;
939         DOSFS_UnixTimeToFileTime( (time_t)0, &entry->ftCreationTime, 0 );
940         DOSFS_UnixTimeToFileTime( (time_t)0, &entry->ftLastAccessTime, 0 );
941         DOSFS_UnixTimeToFileTime( (time_t)0, &entry->ftLastWriteTime, 0 );
942         entry->nFileSizeHigh     = 0;
943         entry->nFileSizeLow      = 0;
944         entry->dwReserved0       = 0;
945         entry->dwReserved1       = 0;
946         DOSFS_ToDosDTAFormat( DRIVE_GetLabel( drive ), entry->cFileName );
947         strcpy( entry->cAlternateFileName, entry->cFileName );
948         return 1;
949     }
950
951     /* Check the cached directory */
952     if (dir && !strcmp( buffer, path ) && (cur_pos <= skip)) skip -= cur_pos;
953     else  /* Not in the cache, open it anew */
954     {
955         const char *drive_path;
956         dprintf_dosfs( stddeb, "DOSFS_FindNext: cache miss, path=%s skip=%d buf=%s cur=%d\n",
957                        path, skip, buffer, cur_pos );
958         cur_pos = skip;
959         if (dir) DOSFS_CloseDir(dir);
960         if (!*path) path = "/";
961         if (!(dir = DOSFS_OpenDir(path))) return 0;
962         drive_path = path + strlen(DRIVE_GetRoot(drive));
963         while ((*drive_path == '/') || (*drive_path == '\\')) drive_path++;
964         drive_root = !*drive_path;
965         dprintf_dosfs(stddeb, "DOSFS_FindNext: drive_root = %d\n", drive_root);
966         lstrcpyn32A( buffer, path, sizeof(buffer) - 1 );
967     }
968     strcat( buffer, "/" );
969     p = buffer + strlen(buffer);
970     attr |= FA_UNUSED | FA_ARCHIVE | FA_RDONLY;
971     flags = DRIVE_GetFlags( drive );
972
973     while (DOSFS_ReadDir( dir, &long_name, &short_name ))
974     {
975         if (skip-- > 0) continue;
976         count++;
977
978         /* Don't return '.' and '..' in the root of the drive */
979         if (drive_root && (long_name[0] == '.') &&
980             (!long_name[1] || ((long_name[1] == '.') && !long_name[2])))
981             continue;
982
983         /* Check the long mask */
984
985         if (long_mask)
986         {
987             if (!DOSFS_MatchLong( long_mask, long_name,
988                                   flags & DRIVE_CASE_SENSITIVE )) continue;
989         }
990
991         /* Check the short mask */
992
993         if (short_mask)
994         {
995             if (!short_name)
996             {
997                 DOSFS_Hash( long_name, dos_name, TRUE,
998                             !(flags & DRIVE_CASE_SENSITIVE) );
999                 short_name = dos_name;
1000             }
1001             if (!DOSFS_MatchShort( short_mask, short_name )) continue;
1002         }
1003
1004         /* Check the file attributes */
1005
1006         lstrcpyn32A( p, long_name, sizeof(buffer) - (int)(p - buffer) );
1007         if (!FILE_Stat( buffer, &info ))
1008         {
1009             fprintf( stderr, "DOSFS_FindNext: can't stat %s\n", buffer );
1010             continue;
1011         }
1012         if (info.dwFileAttributes & ~attr) continue;
1013
1014         /* We now have a matching entry; fill the result and return */
1015
1016         entry->dwFileAttributes = info.dwFileAttributes;
1017         entry->ftCreationTime   = info.ftCreationTime;
1018         entry->ftLastAccessTime = info.ftLastAccessTime;
1019         entry->ftLastWriteTime  = info.ftLastWriteTime;
1020         entry->nFileSizeHigh    = info.nFileSizeHigh;
1021         entry->nFileSizeLow     = info.nFileSizeLow;
1022
1023         if (short_name)
1024             DOSFS_ToDosDTAFormat( short_name, entry->cAlternateFileName );
1025         else
1026             DOSFS_Hash( long_name, entry->cAlternateFileName, FALSE,
1027                         !(flags & DRIVE_CASE_SENSITIVE) );
1028
1029         lstrcpyn32A( entry->cFileName, long_name, sizeof(entry->cFileName) );
1030         if (!(flags & DRIVE_CASE_PRESERVING)) CharLower32A( entry->cFileName );
1031         dprintf_dosfs( stddeb, "DOSFS_FindNext: returning %s (%s) %02lx %ld\n",
1032                        entry->cFileName, entry->cAlternateFileName,
1033                        entry->dwFileAttributes, entry->nFileSizeLow );
1034         cur_pos += count;
1035         p[-1] = '\0';  /* Remove trailing slash in buffer */
1036         return count;
1037     }
1038     DOSFS_CloseDir( dir );
1039     dir = NULL;
1040     return 0;  /* End of directory */
1041 }
1042
1043
1044 /*************************************************************************
1045  *           FindFirstFile16   (KERNEL.413)
1046  */
1047 HANDLE16 FindFirstFile16( LPCSTR path, WIN32_FIND_DATA32A *data )
1048 {
1049     DOS_FULL_NAME full_name;
1050     HGLOBAL16 handle;
1051     FIND_FIRST_INFO *info;
1052
1053     if (!path) return 0;
1054     if (!DOSFS_GetFullName( path, FALSE, &full_name ))
1055         return INVALID_HANDLE_VALUE16;
1056     if (!(handle = GlobalAlloc16( GMEM_MOVEABLE, sizeof(FIND_FIRST_INFO) )))
1057         return INVALID_HANDLE_VALUE16;
1058     info = (FIND_FIRST_INFO *)GlobalLock16( handle );
1059     info->path = HEAP_strdupA( SystemHeap, 0, full_name.long_name );
1060     info->mask = strrchr( info->path, '/' );
1061     *(info->mask++) = '\0';
1062     if (path[0] && (path[1] == ':')) info->drive = toupper(*path) - 'A';
1063     else info->drive = DRIVE_GetCurrentDrive();
1064     info->skip = 0;
1065     GlobalUnlock16( handle );
1066     if (!FindNextFile16( handle, data ))
1067     {
1068         FindClose16( handle );
1069         DOS_ERROR( ER_NoMoreFiles, EC_MediaError, SA_Abort, EL_Disk );
1070         return INVALID_HANDLE_VALUE16;
1071     }
1072     return handle;
1073 }
1074
1075
1076 /*************************************************************************
1077  *           FindFirstFile32A   (KERNEL32.123)
1078  */
1079 HANDLE32 FindFirstFile32A( LPCSTR path, WIN32_FIND_DATA32A *data )
1080 {
1081     HANDLE32 handle = FindFirstFile16( path, data );
1082     if (handle == INVALID_HANDLE_VALUE16) return INVALID_HANDLE_VALUE32;
1083     return handle;
1084 }
1085
1086
1087 /*************************************************************************
1088  *           FindFirstFile32W   (KERNEL32.124)
1089  */
1090 HANDLE32 FindFirstFile32W( LPCWSTR path, WIN32_FIND_DATA32W *data )
1091 {
1092     WIN32_FIND_DATA32A dataA;
1093     LPSTR pathA = HEAP_strdupWtoA( GetProcessHeap(), 0, path );
1094     HANDLE32 handle = FindFirstFile32A( pathA, &dataA );
1095     HeapFree( GetProcessHeap(), 0, pathA );
1096     if (handle != INVALID_HANDLE_VALUE32)
1097     {
1098         data->dwFileAttributes = dataA.dwFileAttributes;
1099         data->ftCreationTime   = dataA.ftCreationTime;
1100         data->ftLastAccessTime = dataA.ftLastAccessTime;
1101         data->ftLastWriteTime  = dataA.ftLastWriteTime;
1102         data->nFileSizeHigh    = dataA.nFileSizeHigh;
1103         data->nFileSizeLow     = dataA.nFileSizeLow;
1104         lstrcpyAtoW( data->cFileName, dataA.cFileName );
1105         lstrcpyAtoW( data->cAlternateFileName, dataA.cAlternateFileName );
1106     }
1107     return handle;
1108 }
1109
1110
1111 /*************************************************************************
1112  *           FindNextFile16   (KERNEL.414)
1113  */
1114 BOOL16 FindNextFile16( HANDLE16 handle, WIN32_FIND_DATA32A *data )
1115 {
1116     FIND_FIRST_INFO *info;
1117     int count;
1118
1119     if (!(info = (FIND_FIRST_INFO *)GlobalLock16( handle )))
1120     {
1121         DOS_ERROR( ER_InvalidHandle, EC_ProgramError, SA_Abort, EL_Disk );
1122         return FALSE;
1123     }
1124     GlobalUnlock16( handle );
1125     if (!info->path)
1126     {
1127         DOS_ERROR( ER_NoMoreFiles, EC_MediaError, SA_Abort, EL_Disk );
1128         return FALSE;
1129     }
1130     if (!(count = DOSFS_FindNext( info->path, NULL, info->mask, info->drive,
1131                                   0xff, info->skip, data )))
1132     {
1133         HeapFree( SystemHeap, 0, info->path );
1134         info->path = info->mask = NULL;
1135         DOS_ERROR( ER_NoMoreFiles, EC_MediaError, SA_Abort, EL_Disk );
1136         return FALSE;
1137     }
1138     info->skip += count;
1139     return TRUE;
1140 }
1141
1142
1143 /*************************************************************************
1144  *           FindNextFile32A   (KERNEL32.126)
1145  */
1146 BOOL32 FindNextFile32A( HANDLE32 handle, WIN32_FIND_DATA32A *data )
1147 {
1148     return FindNextFile16( handle, data );
1149 }
1150
1151
1152 /*************************************************************************
1153  *           FindNextFile32W   (KERNEL32.127)
1154  */
1155 BOOL32 FindNextFile32W( HANDLE32 handle, WIN32_FIND_DATA32W *data )
1156 {
1157     WIN32_FIND_DATA32A dataA;
1158     if (!FindNextFile32A( handle, &dataA )) return FALSE;
1159     data->dwFileAttributes = dataA.dwFileAttributes;
1160     data->ftCreationTime   = dataA.ftCreationTime;
1161     data->ftLastAccessTime = dataA.ftLastAccessTime;
1162     data->ftLastWriteTime  = dataA.ftLastWriteTime;
1163     data->nFileSizeHigh    = dataA.nFileSizeHigh;
1164     data->nFileSizeLow     = dataA.nFileSizeLow;
1165     lstrcpyAtoW( data->cFileName, dataA.cFileName );
1166     lstrcpyAtoW( data->cAlternateFileName, dataA.cAlternateFileName );
1167     return TRUE;
1168 }
1169
1170
1171 /*************************************************************************
1172  *           FindClose16   (KERNEL.415)
1173  */
1174 BOOL16 FindClose16( HANDLE16 handle )
1175 {
1176     FIND_FIRST_INFO *info;
1177
1178     if ((handle == INVALID_HANDLE_VALUE16) ||
1179         !(info = (FIND_FIRST_INFO *)GlobalLock16( handle )))
1180     {
1181         DOS_ERROR( ER_InvalidHandle, EC_ProgramError, SA_Abort, EL_Disk );
1182         return FALSE;
1183     }
1184     if (info->path) HeapFree( SystemHeap, 0, info->path );
1185     GlobalUnlock16( handle );
1186     GlobalFree16( handle );
1187     return TRUE;
1188 }
1189
1190
1191 /*************************************************************************
1192  *           FindClose32   (KERNEL32.119)
1193  */
1194 BOOL32 FindClose32( HANDLE32 handle )
1195 {
1196     return FindClose16( (HANDLE16)handle );
1197 }
1198
1199
1200 /***********************************************************************
1201  *           DOSFS_UnixTimeToFileTime
1202  *
1203  * Convert a Unix time to FILETIME format.
1204  * The FILETIME structure is a 64-bit value representing the number of
1205  * 100-nanosecond intervals since January 1, 1601.
1206  * 'remainder' is the fraction of 100-ns intervals smaller than 1 second
1207  * that couldn't be stored in the time_t value.
1208  */
1209 void DOSFS_UnixTimeToFileTime( time_t unix_time, FILETIME *filetime,
1210                                DWORD remainder )
1211 {
1212     /* FIXME :-) */
1213     filetime->dwLowDateTime  = unix_time;
1214     filetime->dwHighDateTime = 0;
1215 }
1216
1217
1218 /***********************************************************************
1219  *           DOSFS_FileTimeToUnixTime
1220  *
1221  * Convert a FILETIME format to Unix time.
1222  * If not NULL, 'remainder' contains the fractional part of the filetime.
1223  */
1224 time_t DOSFS_FileTimeToUnixTime( const FILETIME *filetime, DWORD *remainder )
1225 {
1226     /* FIXME :-) */
1227     if (remainder) *remainder = 0;
1228     return filetime->dwLowDateTime;
1229 }
1230
1231
1232 /***********************************************************************
1233  *           DosDateTimeToFileTime   (KERNEL32.76)
1234  */
1235 BOOL32 DosDateTimeToFileTime( WORD fatdate, WORD fattime, LPFILETIME ft )
1236 {
1237     struct tm newtm;
1238
1239     newtm.tm_sec  = (fattime & 0x1f) * 2;
1240     newtm.tm_min  = (fattime >> 5) & 0x3f;
1241     newtm.tm_hour = (fattime >> 11);
1242     newtm.tm_mday = (fatdate & 0x1f);
1243     newtm.tm_mon  = ((fatdate >> 5) & 0x0f) - 1;
1244     newtm.tm_year = (fatdate >> 9) + 80;
1245     DOSFS_UnixTimeToFileTime( mktime( &newtm ), ft, 0 );
1246     return TRUE;
1247 }
1248
1249
1250 /***********************************************************************
1251  *           FileTimeToDosDateTime   (KERNEL32.111)
1252  */
1253 BOOL32 FileTimeToDosDateTime( const FILETIME *ft, LPWORD fatdate,
1254                               LPWORD fattime )
1255 {
1256     time_t unixtime = DOSFS_FileTimeToUnixTime( ft, NULL );
1257     struct tm *tm = localtime( &unixtime );
1258     if (fattime)
1259         *fattime = (tm->tm_hour << 11) + (tm->tm_min << 5) + (tm->tm_sec / 2);
1260     if (fatdate)
1261         *fatdate = ((tm->tm_year - 80) << 9) + ((tm->tm_mon + 1) << 5)
1262                    + tm->tm_mday;
1263     return TRUE;
1264 }
1265
1266
1267 /***********************************************************************
1268  *           LocalFileTimeToFileTime   (KERNEL32.373)
1269  */
1270 BOOL32 LocalFileTimeToFileTime( const FILETIME *localft, LPFILETIME utcft )
1271 {
1272     struct tm *xtm;
1273     DWORD remainder;
1274
1275     /* convert from local to UTC. Perhaps not correct. FIXME */
1276     time_t unixtime = DOSFS_FileTimeToUnixTime( localft, &remainder );
1277     xtm = gmtime( &unixtime );
1278     DOSFS_UnixTimeToFileTime( mktime(xtm), utcft, remainder );
1279     return TRUE; 
1280 }
1281
1282
1283 /***********************************************************************
1284  *           FileTimeToLocalFileTime   (KERNEL32.112)
1285  */
1286 BOOL32 FileTimeToLocalFileTime( const FILETIME *utcft, LPFILETIME localft )
1287 {
1288     struct tm *xtm;
1289     DWORD remainder;
1290
1291     /* convert from UTC to local. Perhaps not correct. FIXME */
1292     time_t unixtime = DOSFS_FileTimeToUnixTime( utcft, &remainder );
1293     xtm = localtime( &unixtime );
1294     DOSFS_UnixTimeToFileTime( mktime(xtm), localft, remainder );
1295     return TRUE; 
1296 }
1297
1298
1299 /***********************************************************************
1300  *           FileTimeToSystemTime   (KERNEL32.113)
1301  */
1302 BOOL32 FileTimeToSystemTime( const FILETIME *ft, LPSYSTEMTIME syst )
1303 {
1304     struct tm *xtm;
1305     DWORD remainder;
1306     time_t xtime = DOSFS_FileTimeToUnixTime( ft, &remainder );
1307     xtm = gmtime(&xtime);
1308     syst->wYear         = xtm->tm_year;
1309     syst->wMonth        = xtm->tm_mon;
1310     syst->wDayOfWeek    = xtm->tm_wday;
1311     syst->wDay          = xtm->tm_mday;
1312     syst->wHour         = xtm->tm_hour;
1313     syst->wMinute       = xtm->tm_min;
1314     syst->wSecond       = xtm->tm_sec;
1315     syst->wMilliseconds = remainder / 10000;
1316     return TRUE; 
1317 }
1318
1319 /***********************************************************************
1320  *           QueryDosDeviceA   (KERNEL32.413)
1321  *
1322  * returns array of strings terminated by \0, terminated by \0
1323  */
1324 DWORD
1325 QueryDosDevice32A(LPCSTR devname,LPSTR target,DWORD bufsize)
1326 {
1327     LPSTR s;
1328     char  buffer[200];
1329
1330     dprintf_dosfs(stddeb,"QueryDosDevice(%s,...)\n",devname?devname:"<null>");
1331     if (!devname) {
1332         /* return known MSDOS devices */
1333         lstrcpy32A(buffer,"CON COM1 COM2 LPT1 NUL ");
1334         while ((s=strchr(buffer,' ')))
1335                 *s='\0';
1336
1337         lstrcpyn32A(target,buffer,bufsize);
1338         return strlen(buffer);
1339     }
1340     lstrcpy32A(buffer,"\\DEV\\");
1341     lstrcat32A(buffer,devname);
1342     if ((s=strchr(buffer,':'))) *s='\0';
1343     lstrcpyn32A(target,buffer,bufsize);
1344     return strlen(buffer);
1345 }
1346
1347
1348 /***********************************************************************
1349  *           QueryDosDeviceW   (KERNEL32.414)
1350  *
1351  * returns array of strings terminated by \0, terminated by \0
1352  */
1353 DWORD
1354 QueryDosDevice32W(LPCWSTR devname,LPWSTR target,DWORD bufsize)
1355 {
1356     LPSTR devnameA = devname?HEAP_strdupWtoA(GetProcessHeap(),0,devname):NULL;
1357     LPSTR targetA = (LPSTR)HEAP_xalloc(GetProcessHeap(),0,bufsize);
1358     DWORD ret = QueryDosDevice32A(devnameA,targetA,bufsize);
1359
1360     lstrcpynAtoW(target,targetA,bufsize);
1361     if (devnameA) HeapFree(GetProcessHeap(),0,devnameA);
1362     if (targetA) HeapFree(GetProcessHeap(),0,targetA);
1363     return ret;
1364 }
1365
1366
1367 /***********************************************************************
1368  *           SystemTimeToFileTime   (KERNEL32.526)
1369  */
1370 BOOL32 SystemTimeToFileTime( const SYSTEMTIME *syst, LPFILETIME ft )
1371 {
1372     struct tm xtm;
1373
1374     xtm.tm_year = syst->wYear;
1375     xtm.tm_mon  = syst->wMonth;
1376     xtm.tm_wday = syst->wDayOfWeek;
1377     xtm.tm_mday = syst->wDay;
1378     xtm.tm_hour = syst->wHour;
1379     xtm.tm_min  = syst->wMinute;
1380     xtm.tm_sec  = syst->wSecond;
1381     DOSFS_UnixTimeToFileTime( mktime(&xtm), ft, syst->wMilliseconds * 10000 );
1382     return TRUE; 
1383 }