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