Release 980315
[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 <errno.h>
12 #include <fcntl.h>
13 #include <string.h>
14 #include <stdlib.h>
15 #include <stdio.h>
16 #include <sys/stat.h>
17 #include <sys/ioctl.h>
18 #include <time.h>
19 #include <unistd.h>
20 #if defined(__svr4__) || defined(_SCO_DS)
21 #include <sys/statfs.h>
22 #endif
23
24 #include "windows.h"
25 #include "winerror.h"
26 #include "drive.h"
27 #include "file.h"
28 #include "heap.h"
29 #include "msdos.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[] = {
53 "CON","PRN","NUL","AUX","LPT1","LPT2","LPT3","LPT4","COM1","COM2","COM3","COM4",
54 };
55
56
57 #define GET_DRIVE(path) \
58     (((path)[1] == ':') ? toupper((path)[0]) - 'A' : DOSFS_CurDrive)
59
60     /* DOS extended error status */
61 WORD DOS_ExtendedError;
62 BYTE DOS_ErrorClass;
63 BYTE DOS_ErrorAction;
64 BYTE DOS_ErrorLocus;
65
66 /* Info structure for FindFirstFile handle */
67 typedef struct
68 {
69     LPSTR path;
70     LPSTR mask;
71     int   drive;
72     int   skip;
73 } FIND_FIRST_INFO;
74
75
76 /* Directory info for DOSFS_ReadDir */
77 typedef struct
78 {
79     DIR           *dir;
80 #ifdef VFAT_IOCTL_READDIR_BOTH
81     int            fd;
82     char           short_name[12];
83     KERNEL_DIRENT  dirent[2];
84 #endif
85 } DOS_DIR;
86
87
88 /***********************************************************************
89  *           DOSFS_ValidDOSName
90  *
91  * Return 1 if Unix file 'name' is also a valid MS-DOS name
92  * (i.e. contains only valid DOS chars, lower-case only, fits in 8.3 format).
93  * File name can be terminated by '\0', '\\' or '/'.
94  */
95 static int DOSFS_ValidDOSName( const char *name, int ignore_case )
96 {
97     static const char invalid_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" INVALID_DOS_CHARS;
98     const char *p = name;
99     const char *invalid = ignore_case ? (invalid_chars + 26) : invalid_chars;
100     int len = 0;
101
102     if (*p == '.')
103     {
104         /* Check for "." and ".." */
105         p++;
106         if (*p == '.') p++;
107         /* All other names beginning with '.' are invalid */
108         return (IS_END_OF_NAME(*p));
109     }
110     while (!IS_END_OF_NAME(*p))
111     {
112         if (strchr( invalid, *p )) return 0;  /* Invalid char */
113         if (*p == '.') break;  /* Start of the extension */
114         if (++len > 8) return 0;  /* Name too long */
115         p++;
116     }
117     if (*p != '.') return 1;  /* End of name */
118     p++;
119     if (IS_END_OF_NAME(*p)) return 0;  /* Empty extension not allowed */
120     len = 0;
121     while (!IS_END_OF_NAME(*p))
122     {
123         if (strchr( invalid, *p )) return 0;  /* Invalid char */
124         if (*p == '.') return 0;  /* Second extension not allowed */
125         if (++len > 3) return 0;  /* Extension too long */
126         p++;
127     }
128     return 1;
129 }
130
131
132 /***********************************************************************
133  *           DOSFS_ToDosFCBFormat
134  *
135  * Convert a file name to DOS FCB format (8+3 chars, padded with blanks),
136  * expanding wild cards and converting to upper-case in the process.
137  * File name can be terminated by '\0', '\\' or '/'.
138  * Return FALSE if the name is not a valid DOS name.
139  * 'buffer' must be at least 12 characters long.
140  */
141 BOOL32 DOSFS_ToDosFCBFormat( LPCSTR name, LPSTR buffer )
142 {
143     static const char invalid_chars[] = INVALID_DOS_CHARS;
144     const char *p = name;
145     int i;
146
147     /* Check for "." and ".." */
148     if (*p == '.')
149     {
150         p++;
151         strcpy( buffer, ".          " );
152         if (*p == '.')
153         {
154             buffer[1] = '.';
155             p++;
156         }
157         return (!*p || (*p == '/') || (*p == '\\'));
158     }
159
160     for (i = 0; i < 8; i++)
161     {
162         switch(*p)
163         {
164         case '\0':
165         case '\\':
166         case '/':
167         case '.':
168             buffer[i] = ' ';
169             break;
170         case '?':
171             p++;
172             /* fall through */
173         case '*':
174             buffer[i] = '?';
175             break;
176         default:
177             if (strchr( invalid_chars, *p )) return FALSE;
178             buffer[i] = toupper(*p);
179             p++;
180             break;
181         }
182     }
183
184     if (*p == '*')
185     {
186         /* Skip all chars after wildcard up to first dot */
187         while (*p && (*p != '/') && (*p != '\\') && (*p != '.')) p++;
188     }
189     else
190     {
191         /* Check if name too long */
192         if (*p && (*p != '/') && (*p != '\\') && (*p != '.')) return FALSE;
193     }
194     if (*p == '.') p++;  /* Skip dot */
195
196     for (i = 8; i < 11; i++)
197     {
198         switch(*p)
199         {
200         case '\0':
201         case '\\':
202         case '/':
203             buffer[i] = ' ';
204             break;
205         case '.':
206             return FALSE;  /* Second extension not allowed */
207         case '?':
208             p++;
209             /* fall through */
210         case '*':
211             buffer[i] = '?';
212             break;
213         default:
214             if (strchr( invalid_chars, *p )) return FALSE;
215             buffer[i] = toupper(*p);
216             p++;
217             break;
218         }
219     }
220     buffer[11] = '\0';
221     return TRUE;
222 }
223
224
225 /***********************************************************************
226  *           DOSFS_ToDosDTAFormat
227  *
228  * Convert a file name from FCB to DTA format (name.ext, null-terminated)
229  * converting to upper-case in the process.
230  * File name can be terminated by '\0', '\\' or '/'.
231  * 'buffer' must be at least 13 characters long.
232  */
233 static void DOSFS_ToDosDTAFormat( LPCSTR name, LPSTR buffer )
234 {
235     char *p;
236
237     memcpy( buffer, name, 8 );
238     for (p = buffer + 8; (p > buffer) && (p[-1] == ' '); p--);
239     *p++ = '.';
240     memcpy( p, name + 8, 3 );
241     for (p += 3; p[-1] == ' '; p--);
242     if (p[-1] == '.') p--;
243     *p = '\0';
244 }
245
246
247 /***********************************************************************
248  *           DOSFS_MatchShort
249  *
250  * Check a DOS file name against a mask (both in FCB format).
251  */
252 static int DOSFS_MatchShort( const char *mask, const char *name )
253 {
254     int i;
255     for (i = 11; i > 0; i--, mask++, name++)
256         if ((*mask != '?') && (*mask != *name)) return 0;
257     return 1;
258 }
259
260
261 /***********************************************************************
262  *           DOSFS_MatchLong
263  *
264  * Check a long file name against a mask.
265  */
266 static int DOSFS_MatchLong( const char *mask, const char *name,
267                             int case_sensitive )
268 {
269     if (!strcmp( mask, "*.*" )) return 1;
270     while (*name && *mask)
271     {
272         if (*mask == '*')
273         {
274             mask++;
275             while (*mask == '*') mask++;  /* Skip consecutive '*' */
276             if (!*mask) return 1;
277             if (case_sensitive) while (*name && (*name != *mask)) name++;
278             else while (*name && (toupper(*name) != toupper(*mask))) name++;
279             if (!*name) return 0;
280         }
281         else if (*mask != '?')
282         {
283             if (case_sensitive)
284             {
285                 if (*mask != *name) return 0;
286             }
287             else if (toupper(*mask) != toupper(*name)) return 0;
288         }
289         mask++;
290         name++;
291     }
292     return (!*name && !*mask);
293 }
294
295
296 /***********************************************************************
297  *           DOSFS_OpenDir
298  */
299 static DOS_DIR *DOSFS_OpenDir( LPCSTR path )
300 {
301     DOS_DIR *dir = HeapAlloc( SystemHeap, 0, sizeof(*dir) );
302     if (!dir)
303     {
304         DOS_ERROR( ER_OutOfMemory, EC_OutOfResource, SA_Abort, EL_Memory );
305         return NULL;
306     }
307
308 #ifdef VFAT_IOCTL_READDIR_BOTH
309
310     /* Check if the VFAT ioctl is supported on this directory */
311
312     if ((dir->fd = open( path, O_RDONLY )) != -1)
313     {
314         if (ioctl( dir->fd, VFAT_IOCTL_READDIR_BOTH, (long)dir->dirent ) == -1)
315         {
316             close( dir->fd );
317             dir->fd = -1;
318         }
319         else
320         {
321             /* Set the file pointer back at the start of the directory */
322             lseek( dir->fd, 0, SEEK_SET );
323             dir->dir = NULL;
324             return dir;
325         }
326     }
327 #endif  /* VFAT_IOCTL_READDIR_BOTH */
328
329     /* Now use the standard opendir/readdir interface */
330
331     if (!(dir->dir = opendir( path )))
332     {
333         HeapFree( SystemHeap, 0, dir );
334         return NULL;
335     }
336     return dir;
337 }
338
339
340 /***********************************************************************
341  *           DOSFS_CloseDir
342  */
343 static void DOSFS_CloseDir( DOS_DIR *dir )
344 {
345 #ifdef VFAT_IOCTL_READDIR_BOTH
346     if (dir->fd != -1) close( dir->fd );
347 #endif  /* VFAT_IOCTL_READDIR_BOTH */
348     if (dir->dir) closedir( dir->dir );
349     HeapFree( SystemHeap, 0, dir );
350 }
351
352
353 /***********************************************************************
354  *           DOSFS_ReadDir
355  */
356 static BOOL32 DOSFS_ReadDir( DOS_DIR *dir, LPCSTR *long_name,
357                              LPCSTR *short_name )
358 {
359     struct dirent *dirent;
360
361 #ifdef VFAT_IOCTL_READDIR_BOTH
362     if (dir->fd != -1)
363     {
364         if (ioctl( dir->fd, VFAT_IOCTL_READDIR_BOTH, (long)dir->dirent ) != -1) {
365             if (!dir->dirent[0].d_reclen) return FALSE;
366             if (!DOSFS_ToDosFCBFormat( dir->dirent[0].d_name, dir->short_name ))
367                 dir->short_name[0] = '\0';
368             *short_name = dir->short_name;
369             if (dir->dirent[1].d_name[0]) *long_name = dir->dirent[1].d_name;
370             else *long_name = dir->dirent[0].d_name;
371             return TRUE;
372         }
373     }
374 #endif  /* VFAT_IOCTL_READDIR_BOTH */
375
376     if (!(dirent = readdir( dir->dir ))) return FALSE;
377     *long_name  = dirent->d_name;
378     *short_name = NULL;
379     return TRUE;
380 }
381
382
383 /***********************************************************************
384  *           DOSFS_Hash
385  *
386  * Transform a Unix file name into a hashed DOS name. If the name is a valid
387  * DOS name, it is converted to upper-case; otherwise it is replaced by a
388  * hashed version that fits in 8.3 format.
389  * File name can be terminated by '\0', '\\' or '/'.
390  * 'buffer' must be at least 13 characters long.
391  */
392 static void DOSFS_Hash( LPCSTR name, LPSTR buffer, BOOL32 dir_format,
393                         BOOL32 ignore_case )
394 {
395     static const char invalid_chars[] = INVALID_DOS_CHARS "~.";
396     static const char hash_chars[32] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345";
397
398     const char *p, *ext;
399     char *dst;
400     unsigned short hash;
401     int i;
402
403     if (dir_format) strcpy( buffer, "           " );
404
405     if (DOSFS_ValidDOSName( name, ignore_case ))
406     {
407         /* Check for '.' and '..' */
408         if (*name == '.')
409         {
410             buffer[0] = '.';
411             if (!dir_format) buffer[1] = buffer[2] = '\0';
412             if (name[1] == '.') buffer[1] = '.';
413             return;
414         }
415
416         /* Simply copy the name, converting to uppercase */
417
418         for (dst = buffer; !IS_END_OF_NAME(*name) && (*name != '.'); name++)
419             *dst++ = toupper(*name);
420         if (*name == '.')
421         {
422             if (dir_format) dst = buffer + 8;
423             else *dst++ = '.';
424             for (name++; !IS_END_OF_NAME(*name); name++)
425                 *dst++ = toupper(*name);
426         }
427         if (!dir_format) *dst = '\0';
428         return;
429     }
430
431     /* Compute the hash code of the file name */
432     /* If you know something about hash functions, feel free to */
433     /* insert a better algorithm here... */
434     if (ignore_case)
435     {
436         for (p = name, hash = 0xbeef; !IS_END_OF_NAME(p[1]); p++)
437             hash = (hash<<3) ^ (hash>>5) ^ tolower(*p) ^ (tolower(p[1]) << 8);
438         hash = (hash<<3) ^ (hash>>5) ^ tolower(*p); /* Last character*/
439     }
440     else
441     {
442         for (p = name, hash = 0xbeef; !IS_END_OF_NAME(p[1]); p++)
443             hash = (hash << 3) ^ (hash >> 5) ^ *p ^ (p[1] << 8);
444         hash = (hash << 3) ^ (hash >> 5) ^ *p;  /* Last character */
445     }
446
447     /* Find last dot for start of the extension */
448     for (p = name+1, ext = NULL; !IS_END_OF_NAME(*p); p++)
449         if (*p == '.') ext = p;
450     if (ext && IS_END_OF_NAME(ext[1]))
451         ext = NULL;  /* Empty extension ignored */
452
453     /* Copy first 4 chars, replacing invalid chars with '_' */
454     for (i = 4, p = name, dst = buffer; i > 0; i--, p++)
455     {
456         if (IS_END_OF_NAME(*p) || (p == ext)) break;
457         *dst++ = strchr( invalid_chars, *p ) ? '_' : toupper(*p);
458     }
459     /* Pad to 5 chars with '~' */
460     while (i-- >= 0) *dst++ = '~';
461
462     /* Insert hash code converted to 3 ASCII chars */
463     *dst++ = hash_chars[(hash >> 10) & 0x1f];
464     *dst++ = hash_chars[(hash >> 5) & 0x1f];
465     *dst++ = hash_chars[hash & 0x1f];
466
467     /* Copy the first 3 chars of the extension (if any) */
468     if (ext)
469     {
470         if (!dir_format) *dst++ = '.';
471         for (i = 3, ext++; (i > 0) && !IS_END_OF_NAME(*ext); i--, ext++)
472             *dst++ = strchr( invalid_chars, *ext ) ? '_' : toupper(*ext);
473     }
474     if (!dir_format) *dst = '\0';
475 }
476
477
478 /***********************************************************************
479  *           DOSFS_FindUnixName
480  *
481  * Find the Unix file name in a given directory that corresponds to
482  * a file name (either in Unix or DOS format).
483  * File name can be terminated by '\0', '\\' or '/'.
484  * Return TRUE if OK, FALSE if no file name matches.
485  *
486  * 'long_buf' must be at least 'long_len' characters long. If the long name
487  * turns out to be larger than that, the function returns FALSE.
488  * 'short_buf' must be at least 13 characters long.
489  */
490 BOOL32 DOSFS_FindUnixName( LPCSTR path, LPCSTR name, LPSTR long_buf,
491                            INT32 long_len, LPSTR short_buf, BOOL32 ignore_case)
492 {
493     DOS_DIR *dir;
494     LPCSTR long_name, short_name;
495     char dos_name[12], tmp_buf[13];
496     BOOL32 ret;
497
498     const char *p = strchr( name, '/' );
499     int len = p ? (int)(p - name) : strlen(name);
500     if ((p = strchr( name, '\\' ))) len = MIN( (int)(p - name), len );
501     if (long_len < len + 1) return FALSE;
502
503     TRACE(dosfs, "%s,%s\n", path, name );
504
505     if (!DOSFS_ToDosFCBFormat( name, dos_name )) dos_name[0] = '\0';
506
507     if (!(dir = DOSFS_OpenDir( path )))
508     {
509         WARN(dosfs, "(%s,%s): can't open dir: %s\n",
510                        path, name, strerror(errno) );
511         return FALSE;
512     }
513
514     while ((ret = DOSFS_ReadDir( dir, &long_name, &short_name )))
515     {
516         /* Check against Unix name */
517         if (len == strlen(long_name))
518         {
519             if (!ignore_case)
520             {
521                 if (!lstrncmp32A( long_name, name, len )) break;
522             }
523             else
524             {
525                 if (!lstrncmpi32A( long_name, name, len )) break;
526             }
527         }
528         if (dos_name[0])
529         {
530             /* Check against hashed DOS name */
531             if (!short_name)
532             {
533                 DOSFS_Hash( long_name, tmp_buf, TRUE, ignore_case );
534                 short_name = tmp_buf;
535             }
536             if (!strcmp( dos_name, short_name )) break;
537         }
538     }
539     if (ret)
540     {
541         if (long_buf) strcpy( long_buf, long_name );
542         if (short_buf)
543         {
544             if (short_name)
545                 DOSFS_ToDosDTAFormat( short_name, short_buf );
546             else
547                 DOSFS_Hash( long_name, short_buf, FALSE, ignore_case );
548         }
549         TRACE(dosfs, "(%s,%s) -> %s (%s)\n",
550                      path, name, long_name, short_buf ? short_buf : "***");
551     }
552     else
553         WARN(dosfs, "'%s' not found in '%s'\n", name, path);
554     DOSFS_CloseDir( dir );
555     return ret;
556 }
557
558
559 /***********************************************************************
560  *           DOSFS_IsDevice
561  *
562  * Check if a DOS file name represents a DOS device.
563  */
564 BOOL32 DOSFS_IsDevice( const char *name )
565 {
566     int i;
567     const char *p;
568
569     if (name[0] && (name[1] == ':')) name += 2;
570     if ((p = strrchr( name, '/' ))) name = p + 1;
571     if ((p = strrchr( name, '\\' ))) name = p + 1;
572     for (i = 0; i < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0]); i++)
573     {
574         const char *dev = DOSFS_Devices[i];
575         if (!lstrncmpi32A( dev, name, strlen(dev) ))
576         {
577             p = name + strlen( dev );
578             if (!*p || (*p == '.')) return TRUE;
579         }
580     }
581     return FALSE;
582 }
583
584 /***********************************************************************
585  *           DOSFS_OpenDevice
586  *
587  * Open a DOS device. This might not map 1:1 into the UNIX device concept.
588  */
589 HFILE32 DOSFS_OpenDevice( const char *name, int unixmode )
590 {
591     int i;
592     const char *p;
593
594     if (name[0] && (name[1] == ':')) name += 2;
595     if ((p = strrchr( name, '/' ))) name = p + 1;
596     if ((p = strrchr( name, '\\' ))) name = p + 1;
597     for (i = 0; i < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0]); i++)
598     {
599         const char *dev = DOSFS_Devices[i];
600         if (!lstrncmpi32A( dev, name, strlen(dev) ))
601         {
602             p = name + strlen( dev );
603             if (!*p || (*p == '.')) {
604                 /* got it */
605                 if (!strcmp(DOSFS_Devices[i],"NUL"))
606                         return FILE_OpenUnixFile("/dev/null",unixmode);
607                 if (!strcmp(DOSFS_Devices[i],"CON")) {
608                         switch (unixmode) {
609                         case O_RDONLY:
610                                 return GetStdHandle( STD_INPUT_HANDLE );
611                                 break;
612                         case O_WRONLY:
613                                 return GetStdHandle( STD_OUTPUT_HANDLE );
614                                 break;
615                         default:
616                                 FIXME(dosfs,"can't open CON read/write\n");
617                                 return HFILE_ERROR32;
618                                 break;
619                         }
620                 }
621                 /* FIXME: the rest of the devices ... lptX, comX et.al. */
622                 return HFILE_ERROR32;
623             }
624         }
625     }
626     return HFILE_ERROR32;
627 }
628
629
630 /***********************************************************************
631  *           DOSFS_GetPathDrive
632  *
633  * Get the drive specified by a given path name (DOS or Unix format).
634  */
635 static int DOSFS_GetPathDrive( const char **name )
636 {
637     int drive;
638     const char *p = *name;
639
640     if (*p && (p[1] == ':'))
641     {
642         drive = toupper(*p) - 'A';
643         *name += 2;
644     }
645     else if (*p == '/') /* Absolute Unix path? */
646     {
647         if ((drive = DRIVE_FindDriveRoot( name )) == -1)
648         {
649             fprintf( stderr, "Warning: %s not accessible from a DOS drive\n",
650                      *name );
651             /* Assume it really was a DOS name */
652             drive = DRIVE_GetCurrentDrive();            
653         }
654     }
655     else drive = DRIVE_GetCurrentDrive();
656
657     if (!DRIVE_IsValid(drive))
658     {
659         DOS_ERROR( ER_InvalidDrive, EC_MediaError, SA_Abort, EL_Disk );
660         return -1;
661     }
662     return drive;
663 }
664
665
666 /***********************************************************************
667  *           DOSFS_GetFullName
668  *
669  * Convert a file name (DOS or mixed DOS/Unix format) to a valid
670  * Unix name / short DOS name pair.
671  * Return FALSE if one of the path components does not exist. The last path
672  * component is only checked if 'check_last' is non-zero.
673  * The buffers pointed to by 'long_buf' and 'short_buf' must be
674  * at least MAX_PATHNAME_LEN long.
675  */
676 BOOL32 DOSFS_GetFullName( LPCSTR name, BOOL32 check_last, DOS_FULL_NAME *full )
677 {
678     BOOL32 found;
679     UINT32 flags;
680     char *p_l, *p_s, *root;
681
682     TRACE(dosfs, "%s (last=%d)\n",
683                    name, check_last );
684
685     if ((full->drive = DOSFS_GetPathDrive( &name )) == -1) return FALSE;
686     flags = DRIVE_GetFlags( full->drive );
687
688     lstrcpyn32A( full->long_name, DRIVE_GetRoot( full->drive ),
689                  sizeof(full->long_name) );
690     if (full->long_name[1]) root = full->long_name + strlen(full->long_name);
691     else root = full->long_name;  /* root directory */
692
693     strcpy( full->short_name, "A:\\" );
694     full->short_name[0] += full->drive;
695
696     if ((*name == '\\') || (*name == '/'))  /* Absolute path */
697     {
698         while ((*name == '\\') || (*name == '/')) name++;
699     }
700     else  /* Relative path */
701     {
702         lstrcpyn32A( root + 1, DRIVE_GetUnixCwd( full->drive ),
703                      sizeof(full->long_name) - (root - full->long_name) - 1 );
704         if (root[1]) *root = '/';
705         lstrcpyn32A( full->short_name + 3, DRIVE_GetDosCwd( full->drive ),
706                      sizeof(full->short_name) - 3 );
707     }
708
709     p_l = full->long_name[1] ? full->long_name + strlen(full->long_name)
710                              : full->long_name;
711     p_s = full->short_name[3] ? full->short_name + strlen(full->short_name)
712                               : full->short_name + 2;
713     found = TRUE;
714
715     while (*name && found)
716     {
717         /* Check for '.' and '..' */
718
719         if (*name == '.')
720         {
721             if (IS_END_OF_NAME(name[1]))
722             {
723                 name++;
724                 while ((*name == '\\') || (*name == '/')) name++;
725                 continue;
726             }
727             else if ((name[1] == '.') && IS_END_OF_NAME(name[2]))
728             {
729                 name += 2;
730                 while ((*name == '\\') || (*name == '/')) name++;
731                 while ((p_l > root) && (*p_l != '/')) p_l--;
732                 while ((p_s > full->short_name + 2) && (*p_s != '\\')) p_s--;
733                 *p_l = *p_s = '\0';  /* Remove trailing separator */
734                 continue;
735             }
736         }
737
738         /* Make sure buffers are large enough */
739
740         if ((p_s >= full->short_name + sizeof(full->short_name) - 14) ||
741             (p_l >= full->long_name + sizeof(full->long_name) - 1))
742         {
743             DOS_ERROR( ER_PathNotFound, EC_NotFound, SA_Abort, EL_Disk );
744             return FALSE;
745         }
746
747         /* Get the long and short name matching the file name */
748
749         if ((found = DOSFS_FindUnixName( full->long_name, name, p_l + 1,
750                          sizeof(full->long_name) - (p_l - full->long_name) - 1,
751                          p_s + 1, !(flags & DRIVE_CASE_SENSITIVE) )))
752         {
753             *p_l++ = '/';
754             p_l   += strlen(p_l);
755             *p_s++ = '\\';
756             p_s   += strlen(p_s);
757             while (!IS_END_OF_NAME(*name)) name++;
758         }
759         else if (!check_last)
760         {
761             *p_l++ = '/';
762             *p_s++ = '\\';
763             while (!IS_END_OF_NAME(*name) &&
764                    (p_s < full->short_name + sizeof(full->short_name) - 1) &&
765                    (p_l < full->long_name + sizeof(full->long_name) - 1))
766             {
767                 *p_l++ = *p_s++ = tolower(*name);
768                 name++;
769             }
770             *p_l = *p_s = '\0';
771         }
772         while ((*name == '\\') || (*name == '/')) name++;
773     }
774
775     if (!found)
776     {
777         if (check_last)
778         {
779             DOS_ERROR( ER_FileNotFound, EC_NotFound, SA_Abort, EL_Disk );
780             return FALSE;
781         }
782         if (*name)  /* Not last */
783         {
784             DOS_ERROR( ER_PathNotFound, EC_NotFound, SA_Abort, EL_Disk );
785             return FALSE;
786         }
787     }
788     if (!full->long_name[0]) strcpy( full->long_name, "/" );
789     if (!full->short_name[2]) strcpy( full->short_name + 2, "\\" );
790     TRACE(dosfs, "returning %s = %s\n",
791                    full->long_name, full->short_name );
792     return TRUE;
793 }
794
795
796 /***********************************************************************
797  *           GetShortPathName32A   (KERNEL32.271)
798  */
799 DWORD WINAPI GetShortPathName32A( LPCSTR longpath, LPSTR shortpath,
800                                   DWORD shortlen )
801 {
802     DOS_FULL_NAME full_name;
803
804     /* FIXME: is it correct to always return a fully qualified short path? */
805     if (!DOSFS_GetFullName( longpath, TRUE, &full_name )) return 0;
806     lstrcpyn32A( shortpath, full_name.short_name, shortlen );
807     return strlen( full_name.short_name );
808 }
809
810
811 /***********************************************************************
812  *           GetShortPathName32W   (KERNEL32.272)
813  */
814 DWORD WINAPI GetShortPathName32W( LPCWSTR longpath, LPWSTR shortpath,
815                                   DWORD shortlen )
816 {
817     DOS_FULL_NAME full_name;
818     DWORD ret = 0;
819     LPSTR longpathA = HEAP_strdupWtoA( GetProcessHeap(), 0, longpath );
820
821     /* FIXME: is it correct to always return a fully qualified short path? */
822     if (DOSFS_GetFullName( longpathA, TRUE, &full_name ))
823     {
824         ret = strlen( full_name.short_name );
825         lstrcpynAtoW( shortpath, full_name.short_name, shortlen );
826     }
827     HeapFree( GetProcessHeap(), 0, longpathA );
828     return ret;
829 }
830
831
832 /***********************************************************************
833  *           GetLongPathName32A   (KERNEL32.xxx)
834  */
835 DWORD WINAPI GetLongPathName32A( LPCSTR shortpath, LPSTR longpath,
836                                   DWORD longlen )
837 {
838     DOS_FULL_NAME full_name;
839
840     /* FIXME: is it correct to always return a fully qualified short path? */
841     if (!DOSFS_GetFullName( shortpath, TRUE, &full_name )) return 0;
842     lstrcpyn32A( longpath, full_name.long_name, longlen );
843     return strlen( full_name.long_name );
844 }
845
846
847 /***********************************************************************
848  *           GetLongPathName32W   (KERNEL32.269)
849  */
850 DWORD WINAPI GetLongPathName32W( LPCWSTR shortpath, LPWSTR longpath,
851                                   DWORD longlen )
852 {
853     DOS_FULL_NAME full_name;
854     DWORD ret = 0;
855     LPSTR shortpathA = HEAP_strdupWtoA( GetProcessHeap(), 0, shortpath );
856
857     /* FIXME: is it correct to always return a fully qualified short path? */
858     if (DOSFS_GetFullName( shortpathA, TRUE, &full_name ))
859     {
860         ret = strlen( full_name.short_name );
861         lstrcpynAtoW( longpath, full_name.long_name, longlen );
862     }
863     HeapFree( GetProcessHeap(), 0, shortpathA );
864     return ret;
865 }
866
867
868 /***********************************************************************
869  *           DOSFS_DoGetFullPathName
870  *
871  * Implementation of GetFullPathName32A/W.
872  */
873 static DWORD DOSFS_DoGetFullPathName( LPCSTR name, DWORD len, LPSTR result,
874                                       BOOL32 unicode )
875 {
876     char buffer[MAX_PATHNAME_LEN];
877     int drive;
878     char *p;
879
880     TRACE(dosfs, "converting %s\n", name );
881
882     if (!name || !result) return 0;
883
884     if ((drive = DOSFS_GetPathDrive( &name )) == -1) return 0;
885     p = buffer;
886     *p++ = 'A' + drive;
887     *p++ = ':';
888     if (IS_END_OF_NAME(*name) && (*name))  /* Absolute path */
889     {
890         while ((*name == '\\') || (*name == '/')) name++;
891     }
892     else  /* Relative path or empty path */
893     {
894         *p++ = '\\';
895         lstrcpyn32A( p, DRIVE_GetDosCwd(drive), sizeof(buffer) - 3 );
896         if (*p) p += strlen(p); else p--;
897     }
898     if (!*name) /* empty path */
899       *p++ = '\\';
900     *p = '\0';
901
902     while (*name)
903     {
904         if (*name == '.')
905         {
906             if (IS_END_OF_NAME(name[1]))
907             {
908                 name++;
909                 while ((*name == '\\') || (*name == '/')) name++;
910                 continue;
911             }
912             else if ((name[1] == '.') && IS_END_OF_NAME(name[2]))
913             {
914                 name += 2;
915                 while ((*name == '\\') || (*name == '/')) name++;
916                 while ((p > buffer + 2) && (*p != '\\')) p--;
917                 *p = '\0';  /* Remove trailing separator */
918                 continue;
919             }
920         }
921         if (p >= buffer + sizeof(buffer) - 1)
922         {
923             DOS_ERROR( ER_PathNotFound, EC_NotFound, SA_Abort, EL_Disk );
924             return 0;
925         }
926         *p++ = '\\';
927         while (!IS_END_OF_NAME(*name) && (p < buffer + sizeof(buffer) - 1))
928             *p++ = *name++;
929         *p = '\0';
930         while ((*name == '\\') || (*name == '/')) name++;
931     }
932
933     if (!buffer[2])
934     {
935         buffer[2] = '\\';
936         buffer[3] = '\0';
937     }
938     if (!(DRIVE_GetFlags(drive) & DRIVE_CASE_PRESERVING))
939         CharUpper32A( buffer );
940
941     if (unicode) lstrcpynAtoW( (LPWSTR)result, buffer, len );
942     else lstrcpyn32A( result, buffer, len );
943
944     TRACE(dosfs, "returning %s\n", buffer );
945     return strlen(buffer);
946 }
947
948
949 /***********************************************************************
950  *           GetFullPathName32A   (KERNEL32.272)
951  */
952 DWORD WINAPI GetFullPathName32A( LPCSTR name, DWORD len, LPSTR buffer,
953                                  LPSTR *lastpart )
954 {
955     DWORD ret = DOSFS_DoGetFullPathName( name, len, buffer, FALSE );
956     if (ret && lastpart)
957     {
958         LPSTR p = buffer + strlen(buffer);
959         while ((p > buffer + 2) && (*p != '\\')) p--;
960         *lastpart = p + 1;
961     }
962     return ret;
963 }
964
965
966 /***********************************************************************
967  *           GetFullPathName32W   (KERNEL32.273)
968  */
969 DWORD WINAPI GetFullPathName32W( LPCWSTR name, DWORD len, LPWSTR buffer,
970                                  LPWSTR *lastpart )
971 {
972     LPSTR nameA = HEAP_strdupWtoA( GetProcessHeap(), 0, name );
973     DWORD ret = DOSFS_DoGetFullPathName( nameA, len, (LPSTR)buffer, TRUE );
974     HeapFree( GetProcessHeap(), 0, nameA );
975     if (ret && lastpart)
976     {
977         LPWSTR p = buffer + lstrlen32W(buffer);
978         while ((p > buffer + 2) && (*p != '\\')) p--;
979         *lastpart = p + 1;
980     }
981     return ret;
982 }
983
984
985 /***********************************************************************
986  *           DOSFS_FindNext
987  *
988  * Find the next matching file. Return the number of entries read to find
989  * the matching one, or 0 if no more entries.
990  * 'short_mask' is the 8.3 mask (in FCB format), 'long_mask' is the long
991  * file name mask. Either or both can be NULL.
992  */
993 int DOSFS_FindNext( const char *path, const char *short_mask,
994                     const char *long_mask, int drive, BYTE attr,
995                     int skip, WIN32_FIND_DATA32A *entry )
996 {
997     static DOS_DIR *dir = NULL;
998     int count = 0;
999     static char buffer[MAX_PATHNAME_LEN];
1000     static int cur_pos = 0;
1001     static int drive_root = 0;
1002     char *p;
1003     char dos_name[13];
1004     LPCSTR long_name, short_name;
1005     UINT32 flags;
1006     BY_HANDLE_FILE_INFORMATION info;
1007
1008     if ((attr & ~(FA_UNUSED | FA_ARCHIVE | FA_RDONLY)) == FA_LABEL)
1009     {
1010         if (skip) return 0;
1011         entry->dwFileAttributes  = FILE_ATTRIBUTE_LABEL;
1012         DOSFS_UnixTimeToFileTime( (time_t)0, &entry->ftCreationTime, 0 );
1013         DOSFS_UnixTimeToFileTime( (time_t)0, &entry->ftLastAccessTime, 0 );
1014         DOSFS_UnixTimeToFileTime( (time_t)0, &entry->ftLastWriteTime, 0 );
1015         entry->nFileSizeHigh     = 0;
1016         entry->nFileSizeLow      = 0;
1017         entry->dwReserved0       = 0;
1018         entry->dwReserved1       = 0;
1019         DOSFS_ToDosDTAFormat( DRIVE_GetLabel( drive ), entry->cFileName );
1020         strcpy( entry->cAlternateFileName, entry->cFileName );
1021         return 1;
1022     }
1023
1024     /* Check the cached directory */
1025     if (dir && !strcmp( buffer, path ) && (cur_pos <= skip)) skip -= cur_pos;
1026     else  /* Not in the cache, open it anew */
1027     {
1028         const char *drive_path;
1029         TRACE(dosfs, "cache miss, path=%s skip=%d buf=%s cur=%d\n",
1030                        path, skip, buffer, cur_pos );
1031         cur_pos = skip;
1032         if (dir) DOSFS_CloseDir(dir);
1033         if (!*path) path = "/";
1034         if (!(dir = DOSFS_OpenDir(path))) return 0;
1035         drive_path = path + strlen(DRIVE_GetRoot(drive));
1036         while ((*drive_path == '/') || (*drive_path == '\\')) drive_path++;
1037         drive_root = !*drive_path;
1038         TRACE(dosfs, "drive_root = %d\n", drive_root);
1039         lstrcpyn32A( buffer, path, sizeof(buffer) - 1 );
1040     }
1041     strcat( buffer, "/" );
1042     p = buffer + strlen(buffer);
1043     attr |= FA_UNUSED | FA_ARCHIVE | FA_RDONLY;
1044     flags = DRIVE_GetFlags( drive );
1045
1046     while (DOSFS_ReadDir( dir, &long_name, &short_name ))
1047     {
1048         if (skip-- > 0) continue;
1049         count++;
1050
1051         /* Don't return '.' and '..' in the root of the drive */
1052         if (drive_root && (long_name[0] == '.') &&
1053             (!long_name[1] || ((long_name[1] == '.') && !long_name[2])))
1054             continue;
1055
1056         /* Check the long mask */
1057
1058         if (long_mask)
1059         {
1060             if (!DOSFS_MatchLong( long_mask, long_name,
1061                                   flags & DRIVE_CASE_SENSITIVE )) continue;
1062         }
1063
1064         /* Check the short mask */
1065
1066         if (short_mask)
1067         {
1068             if (!short_name)
1069             {
1070                 DOSFS_Hash( long_name, dos_name, TRUE,
1071                             !(flags & DRIVE_CASE_SENSITIVE) );
1072                 short_name = dos_name;
1073             }
1074             if (!DOSFS_MatchShort( short_mask, short_name )) continue;
1075         }
1076
1077         /* Check the file attributes */
1078
1079         lstrcpyn32A( p, long_name, sizeof(buffer) - (int)(p - buffer) );
1080         if (!FILE_Stat( buffer, &info ))
1081         {
1082             WARN(dosfs, "can't stat %s\n", buffer);
1083             continue;
1084         }
1085         if (info.dwFileAttributes & ~attr) continue;
1086
1087         /* We now have a matching entry; fill the result and return */
1088
1089         entry->dwFileAttributes = info.dwFileAttributes;
1090         entry->ftCreationTime   = info.ftCreationTime;
1091         entry->ftLastAccessTime = info.ftLastAccessTime;
1092         entry->ftLastWriteTime  = info.ftLastWriteTime;
1093         entry->nFileSizeHigh    = info.nFileSizeHigh;
1094         entry->nFileSizeLow     = info.nFileSizeLow;
1095
1096         if (short_name)
1097             DOSFS_ToDosDTAFormat( short_name, entry->cAlternateFileName );
1098         else
1099             DOSFS_Hash( long_name, entry->cAlternateFileName, FALSE,
1100                         !(flags & DRIVE_CASE_SENSITIVE) );
1101
1102         lstrcpyn32A( entry->cFileName, long_name, sizeof(entry->cFileName) );
1103         if (!(flags & DRIVE_CASE_PRESERVING)) CharLower32A( entry->cFileName );
1104         TRACE(dosfs, "returning %s (%s) %02lx %ld\n",
1105                        entry->cFileName, entry->cAlternateFileName,
1106                        entry->dwFileAttributes, entry->nFileSizeLow );
1107         cur_pos += count;
1108         p[-1] = '\0';  /* Remove trailing slash in buffer */
1109         return count;
1110     }
1111     DOSFS_CloseDir( dir );
1112     dir = NULL;
1113     return 0;  /* End of directory */
1114 }
1115
1116
1117 /*************************************************************************
1118  *           FindFirstFile16   (KERNEL.413)
1119  */
1120 HANDLE16 WINAPI FindFirstFile16( LPCSTR path, WIN32_FIND_DATA32A *data )
1121 {
1122     DOS_FULL_NAME full_name;
1123     HGLOBAL16 handle;
1124     FIND_FIRST_INFO *info;
1125
1126     if (!path) return 0;
1127     if (!DOSFS_GetFullName( path, FALSE, &full_name ))
1128         return INVALID_HANDLE_VALUE16;
1129     if (!(handle = GlobalAlloc16( GMEM_MOVEABLE, sizeof(FIND_FIRST_INFO) )))
1130         return INVALID_HANDLE_VALUE16;
1131     info = (FIND_FIRST_INFO *)GlobalLock16( handle );
1132     info->path = HEAP_strdupA( SystemHeap, 0, full_name.long_name );
1133     info->mask = strrchr( info->path, '/' );
1134     *(info->mask++) = '\0';
1135     if (path[0] && (path[1] == ':')) info->drive = toupper(*path) - 'A';
1136     else info->drive = DRIVE_GetCurrentDrive();
1137     info->skip = 0;
1138     GlobalUnlock16( handle );
1139     if (!FindNextFile16( handle, data ))
1140     {
1141         FindClose16( handle );
1142         DOS_ERROR( ER_NoMoreFiles, EC_MediaError, SA_Abort, EL_Disk );
1143         return INVALID_HANDLE_VALUE16;
1144     }
1145     return handle;
1146 }
1147
1148
1149 /*************************************************************************
1150  *           FindFirstFile32A   (KERNEL32.123)
1151  */
1152 HANDLE32 WINAPI FindFirstFile32A( LPCSTR path, WIN32_FIND_DATA32A *data )
1153 {
1154     HANDLE32 handle = FindFirstFile16( path, data );
1155     if (handle == INVALID_HANDLE_VALUE16) return INVALID_HANDLE_VALUE32;
1156     return handle;
1157 }
1158
1159
1160 /*************************************************************************
1161  *           FindFirstFile32W   (KERNEL32.124)
1162  */
1163 HANDLE32 WINAPI FindFirstFile32W( LPCWSTR path, WIN32_FIND_DATA32W *data )
1164 {
1165     WIN32_FIND_DATA32A dataA;
1166     LPSTR pathA = HEAP_strdupWtoA( GetProcessHeap(), 0, path );
1167     HANDLE32 handle = FindFirstFile32A( pathA, &dataA );
1168     HeapFree( GetProcessHeap(), 0, pathA );
1169     if (handle != INVALID_HANDLE_VALUE32)
1170     {
1171         data->dwFileAttributes = dataA.dwFileAttributes;
1172         data->ftCreationTime   = dataA.ftCreationTime;
1173         data->ftLastAccessTime = dataA.ftLastAccessTime;
1174         data->ftLastWriteTime  = dataA.ftLastWriteTime;
1175         data->nFileSizeHigh    = dataA.nFileSizeHigh;
1176         data->nFileSizeLow     = dataA.nFileSizeLow;
1177         lstrcpyAtoW( data->cFileName, dataA.cFileName );
1178         lstrcpyAtoW( data->cAlternateFileName, dataA.cAlternateFileName );
1179     }
1180     return handle;
1181 }
1182
1183
1184 /*************************************************************************
1185  *           FindNextFile16   (KERNEL.414)
1186  */
1187 BOOL16 WINAPI FindNextFile16( HANDLE16 handle, WIN32_FIND_DATA32A *data )
1188 {
1189     FIND_FIRST_INFO *info;
1190     int count;
1191
1192     if (!(info = (FIND_FIRST_INFO *)GlobalLock16( handle )))
1193     {
1194         DOS_ERROR( ER_InvalidHandle, EC_ProgramError, SA_Abort, EL_Disk );
1195         return FALSE;
1196     }
1197     GlobalUnlock16( handle );
1198     if (!info->path)
1199     {
1200         DOS_ERROR( ER_NoMoreFiles, EC_MediaError, SA_Abort, EL_Disk );
1201         return FALSE;
1202     }
1203     if (!(count = DOSFS_FindNext( info->path, NULL, info->mask, info->drive,
1204                                   0xff, info->skip, data )))
1205     {
1206         HeapFree( SystemHeap, 0, info->path );
1207         info->path = info->mask = NULL;
1208         DOS_ERROR( ER_NoMoreFiles, EC_MediaError, SA_Abort, EL_Disk );
1209         return FALSE;
1210     }
1211     info->skip += count;
1212     return TRUE;
1213 }
1214
1215
1216 /*************************************************************************
1217  *           FindNextFile32A   (KERNEL32.126)
1218  */
1219 BOOL32 WINAPI FindNextFile32A( HANDLE32 handle, WIN32_FIND_DATA32A *data )
1220 {
1221     return FindNextFile16( handle, data );
1222 }
1223
1224
1225 /*************************************************************************
1226  *           FindNextFile32W   (KERNEL32.127)
1227  */
1228 BOOL32 WINAPI FindNextFile32W( HANDLE32 handle, WIN32_FIND_DATA32W *data )
1229 {
1230     WIN32_FIND_DATA32A dataA;
1231     if (!FindNextFile32A( handle, &dataA )) return FALSE;
1232     data->dwFileAttributes = dataA.dwFileAttributes;
1233     data->ftCreationTime   = dataA.ftCreationTime;
1234     data->ftLastAccessTime = dataA.ftLastAccessTime;
1235     data->ftLastWriteTime  = dataA.ftLastWriteTime;
1236     data->nFileSizeHigh    = dataA.nFileSizeHigh;
1237     data->nFileSizeLow     = dataA.nFileSizeLow;
1238     lstrcpyAtoW( data->cFileName, dataA.cFileName );
1239     lstrcpyAtoW( data->cAlternateFileName, dataA.cAlternateFileName );
1240     return TRUE;
1241 }
1242
1243
1244 /*************************************************************************
1245  *           FindClose16   (KERNEL.415)
1246  */
1247 BOOL16 WINAPI FindClose16( HANDLE16 handle )
1248 {
1249     FIND_FIRST_INFO *info;
1250
1251     if ((handle == INVALID_HANDLE_VALUE16) ||
1252         !(info = (FIND_FIRST_INFO *)GlobalLock16( handle )))
1253     {
1254         DOS_ERROR( ER_InvalidHandle, EC_ProgramError, SA_Abort, EL_Disk );
1255         return FALSE;
1256     }
1257     if (info->path) HeapFree( SystemHeap, 0, info->path );
1258     GlobalUnlock16( handle );
1259     GlobalFree16( handle );
1260     return TRUE;
1261 }
1262
1263
1264 /*************************************************************************
1265  *           FindClose32   (KERNEL32.119)
1266  */
1267 BOOL32 WINAPI FindClose32( HANDLE32 handle )
1268 {
1269     return FindClose16( (HANDLE16)handle );
1270 }
1271
1272
1273 /***********************************************************************
1274  *           DOSFS_UnixTimeToFileTime
1275  *
1276  * Convert a Unix time to FILETIME format.
1277  * The FILETIME structure is a 64-bit value representing the number of
1278  * 100-nanosecond intervals since January 1, 1601, 0:00.
1279  * 'remainder' is the nonnegative number of 100-ns intervals
1280  * corresponding to the time fraction smaller than 1 second that
1281  * couldn't be stored in the time_t value.
1282  */
1283 void DOSFS_UnixTimeToFileTime( time_t unix_time, FILETIME *filetime,
1284                                DWORD remainder )
1285 {
1286     /* NOTES:
1287
1288        CONSTANTS: 
1289        The time difference between 1 January 1601, 00:00:00 and
1290        1 January 1970, 00:00:00 is 369 years, plus the leap years
1291        from 1604 to 1968, excluding 1700, 1800, 1900.
1292        This makes (1968 - 1600) / 4 - 3 = 89 leap days, and a total
1293        of 134774 days.
1294
1295        Any day in that period had 24 * 60 * 60 = 86400 seconds.
1296
1297        The time difference is 134774 * 86400 * 10000000, which can be written
1298        116444736000000000
1299        27111902 * 2^32 + 3577643008
1300        413 * 2^48 + 45534 * 2^32 + 54590 * 2^16 + 32768
1301
1302        If you find that these constants are buggy, please change them in all
1303        instances in both conversion functions.
1304
1305        VERSIONS:
1306        There are two versions, one of them uses long long variables and
1307        is presumably faster but not ISO C. The other one uses standard C
1308        data types and operations but relies on the assumption that negative
1309        numbers are stored as 2's complement (-1 is 0xffff....). If this
1310        assumption is violated, dates before 1970 will not convert correctly.
1311        This should however work on any reasonable architecture where WINE
1312        will run.
1313
1314        DETAILS:
1315        
1316        Take care not to remove the casts. I have tested these functions
1317        (in both versions) for a lot of numbers. I would be interested in
1318        results on other compilers than GCC.
1319
1320        The operations have been designed to account for the possibility
1321        of 64-bit time_t in future UNICES. Even the versions without
1322        internal long long numbers will work if time_t only is 64 bit.
1323        A 32-bit shift, which was necessary for that operation, turned out
1324        not to work correctly in GCC, besides giving the warning. So I
1325        used a double 16-bit shift instead. Numbers are in the ISO version
1326        represented by three limbs, the most significant with 32 bit, the
1327        other two with 16 bit each.
1328
1329        As the modulo-operator % is not well-defined for negative numbers,
1330        negative divisors have been avoided in DOSFS_FileTimeToUnixTime.
1331
1332        There might be quicker ways to do this in C. Certainly so in
1333        assembler.
1334
1335        Claus Fischer, fischer@iue.tuwien.ac.at
1336        */
1337
1338 #if __GNUC__
1339 #  define USE_LONG_LONG 1
1340 #else
1341 #  define USE_LONG_LONG 0
1342 #endif
1343
1344 #if USE_LONG_LONG               /* gcc supports long long type */
1345
1346     long long int t = unix_time;
1347     t *= 10000000;
1348     t += 116444736000000000LL;
1349     t += remainder;
1350     filetime->dwLowDateTime  = (UINT32)t;
1351     filetime->dwHighDateTime = (UINT32)(t >> 32);
1352
1353 #else  /* ISO version */
1354
1355     UINT32 a0;                  /* 16 bit, low    bits */
1356     UINT32 a1;                  /* 16 bit, medium bits */
1357     UINT32 a2;                  /* 32 bit, high   bits */
1358
1359     /* Copy the unix time to a2/a1/a0 */
1360     a0 =  unix_time & 0xffff;
1361     a1 = (unix_time >> 16) & 0xffff;
1362     /* This is obsolete if unix_time is only 32 bits, but it does not hurt.
1363        Do not replace this by >> 32, it gives a compiler warning and it does
1364        not work. */
1365     a2 = (unix_time >= 0 ? (unix_time >> 16) >> 16 :
1366           ~((~unix_time >> 16) >> 16));
1367
1368     /* Multiply a by 10000000 (a = a2/a1/a0)
1369        Split the factor into 10000 * 1000 which are both less than 0xffff. */
1370     a0 *= 10000;
1371     a1 = a1 * 10000 + (a0 >> 16);
1372     a2 = a2 * 10000 + (a1 >> 16);
1373     a0 &= 0xffff;
1374     a1 &= 0xffff;
1375
1376     a0 *= 1000;
1377     a1 = a1 * 1000 + (a0 >> 16);
1378     a2 = a2 * 1000 + (a1 >> 16);
1379     a0 &= 0xffff;
1380     a1 &= 0xffff;
1381
1382     /* Add the time difference and the remainder */
1383     a0 += 32768 + (remainder & 0xffff);
1384     a1 += 54590 + (remainder >> 16   ) + (a0 >> 16);
1385     a2 += 27111902                     + (a1 >> 16);
1386     a0 &= 0xffff;
1387     a1 &= 0xffff;
1388
1389     /* Set filetime */
1390     filetime->dwLowDateTime  = (a1 << 16) + a0;
1391     filetime->dwHighDateTime = a2;
1392 #endif
1393 }
1394
1395
1396 /***********************************************************************
1397  *           DOSFS_FileTimeToUnixTime
1398  *
1399  * Convert a FILETIME format to Unix time.
1400  * If not NULL, 'remainder' contains the fractional part of the filetime,
1401  * in the range of [0..9999999] (even if time_t is negative).
1402  */
1403 time_t DOSFS_FileTimeToUnixTime( const FILETIME *filetime, DWORD *remainder )
1404 {
1405     /* Read the comment in the function DOSFS_UnixTimeToFileTime. */
1406 #if USE_LONG_LONG
1407
1408     long long int t = filetime->dwHighDateTime;
1409     t <<= 32;
1410     t += (UINT32)filetime->dwLowDateTime;
1411     t -= 116444736000000000LL;
1412     if (t < 0)
1413     {
1414         if (remainder) *remainder = 9999999 - (-t - 1) % 10000000;
1415         return -1 - ((-t - 1) / 10000000);
1416     }
1417     else
1418     {
1419         if (remainder) *remainder = t % 10000000;
1420         return t / 10000000;
1421     }
1422
1423 #else  /* ISO version */
1424
1425     UINT32 a0;                  /* 16 bit, low    bits */
1426     UINT32 a1;                  /* 16 bit, medium bits */
1427     UINT32 a2;                  /* 32 bit, high   bits */
1428     UINT32 r;                   /* remainder of division */
1429     unsigned int carry;         /* carry bit for subtraction */
1430     int negative;               /* whether a represents a negative value */
1431
1432     /* Copy the time values to a2/a1/a0 */
1433     a2 =  (UINT32)filetime->dwHighDateTime;
1434     a1 = ((UINT32)filetime->dwLowDateTime ) >> 16;
1435     a0 = ((UINT32)filetime->dwLowDateTime ) & 0xffff;
1436
1437     /* Subtract the time difference */
1438     if (a0 >= 32768           ) a0 -=             32768        , carry = 0;
1439     else                        a0 += (1 << 16) - 32768        , carry = 1;
1440
1441     if (a1 >= 54590    + carry) a1 -=             54590 + carry, carry = 0;
1442     else                        a1 += (1 << 16) - 54590 - carry, carry = 1;
1443
1444     a2 -= 27111902 + carry;
1445     
1446     /* If a is negative, replace a by (-1-a) */
1447     negative = (a2 >= ((UINT32)1) << 31);
1448     if (negative)
1449     {
1450         /* Set a to -a - 1 (a is a2/a1/a0) */
1451         a0 = 0xffff - a0;
1452         a1 = 0xffff - a1;
1453         a2 = ~a2;
1454     }
1455
1456     /* Divide a by 10000000 (a = a2/a1/a0), put the rest into r.
1457        Split the divisor into 10000 * 1000 which are both less than 0xffff. */
1458     a1 += (a2 % 10000) << 16;
1459     a2 /=       10000;
1460     a0 += (a1 % 10000) << 16;
1461     a1 /=       10000;
1462     r   =  a0 % 10000;
1463     a0 /=       10000;
1464
1465     a1 += (a2 % 1000) << 16;
1466     a2 /=       1000;
1467     a0 += (a1 % 1000) << 16;
1468     a1 /=       1000;
1469     r  += (a0 % 1000) * 10000;
1470     a0 /=       1000;
1471
1472     /* If a was negative, replace a by (-1-a) and r by (9999999 - r) */
1473     if (negative)
1474     {
1475         /* Set a to -a - 1 (a is a2/a1/a0) */
1476         a0 = 0xffff - a0;
1477         a1 = 0xffff - a1;
1478         a2 = ~a2;
1479
1480         r  = 9999999 - r;
1481     }
1482
1483     if (remainder) *remainder = r;
1484
1485     /* Do not replace this by << 32, it gives a compiler warning and it does
1486        not work. */
1487     return ((((time_t)a2) << 16) << 16) + (a1 << 16) + a0;
1488 #endif
1489 }
1490
1491
1492 /***********************************************************************
1493  *           DosDateTimeToFileTime   (KERNEL32.76)
1494  */
1495 BOOL32 WINAPI DosDateTimeToFileTime( WORD fatdate, WORD fattime, LPFILETIME ft)
1496 {
1497     struct tm newtm;
1498
1499     newtm.tm_sec  = (fattime & 0x1f) * 2;
1500     newtm.tm_min  = (fattime >> 5) & 0x3f;
1501     newtm.tm_hour = (fattime >> 11);
1502     newtm.tm_mday = (fatdate & 0x1f);
1503     newtm.tm_mon  = ((fatdate >> 5) & 0x0f) - 1;
1504     newtm.tm_year = (fatdate >> 9) + 80;
1505     DOSFS_UnixTimeToFileTime( mktime( &newtm ), ft, 0 );
1506     return TRUE;
1507 }
1508
1509
1510 /***********************************************************************
1511  *           FileTimeToDosDateTime   (KERNEL32.111)
1512  */
1513 BOOL32 WINAPI FileTimeToDosDateTime( const FILETIME *ft, LPWORD fatdate,
1514                                      LPWORD fattime )
1515 {
1516     time_t unixtime = DOSFS_FileTimeToUnixTime( ft, NULL );
1517     struct tm *tm = localtime( &unixtime );
1518     if (fattime)
1519         *fattime = (tm->tm_hour << 11) + (tm->tm_min << 5) + (tm->tm_sec / 2);
1520     if (fatdate)
1521         *fatdate = ((tm->tm_year - 80) << 9) + ((tm->tm_mon + 1) << 5)
1522                    + tm->tm_mday;
1523     return TRUE;
1524 }
1525
1526
1527 /***********************************************************************
1528  *           LocalFileTimeToFileTime   (KERNEL32.373)
1529  */
1530 BOOL32 WINAPI LocalFileTimeToFileTime( const FILETIME *localft,
1531                                        LPFILETIME utcft )
1532 {
1533     struct tm *xtm;
1534     DWORD remainder;
1535
1536     /* convert from local to UTC. Perhaps not correct. FIXME */
1537     time_t unixtime = DOSFS_FileTimeToUnixTime( localft, &remainder );
1538     xtm = gmtime( &unixtime );
1539     DOSFS_UnixTimeToFileTime( mktime(xtm), utcft, remainder );
1540     return TRUE; 
1541 }
1542
1543
1544 /***********************************************************************
1545  *           FileTimeToLocalFileTime   (KERNEL32.112)
1546  */
1547 BOOL32 WINAPI FileTimeToLocalFileTime( const FILETIME *utcft,
1548                                        LPFILETIME localft )
1549 {
1550     DWORD remainder;
1551     /* convert from UTC to local. Perhaps not correct. FIXME */
1552     time_t unixtime = DOSFS_FileTimeToUnixTime( utcft, &remainder );
1553 #ifdef HAVE_TIMEGM
1554     struct tm *xtm = localtime( &unixtime );
1555     time_t localtime;
1556
1557     localtime = timegm(xtm);
1558     DOSFS_UnixTimeToFileTime( localtime, localft, remainder );
1559
1560 #else
1561     struct tm *xtm,*gtm;
1562     time_t time1,time2;
1563
1564     xtm = localtime( &unixtime );
1565     gtm = gmtime( &unixtime );
1566     time1 = mktime(xtm);
1567     time2 = mktime(gtm);
1568     DOSFS_UnixTimeToFileTime( 2*time1-time2, localft, remainder );
1569 #endif
1570     return TRUE; 
1571 }
1572
1573
1574 /***********************************************************************
1575  *           FileTimeToSystemTime   (KERNEL32.113)
1576  */
1577 BOOL32 WINAPI FileTimeToSystemTime( const FILETIME *ft, LPSYSTEMTIME syst )
1578 {
1579     struct tm *xtm;
1580     DWORD remainder;
1581     time_t xtime = DOSFS_FileTimeToUnixTime( ft, &remainder );
1582     xtm = gmtime(&xtime);
1583     syst->wYear         = xtm->tm_year+1900;
1584     syst->wMonth        = xtm->tm_mon + 1;
1585     syst->wDayOfWeek    = xtm->tm_wday;
1586     syst->wDay          = xtm->tm_mday;
1587     syst->wHour         = xtm->tm_hour;
1588     syst->wMinute       = xtm->tm_min;
1589     syst->wSecond       = xtm->tm_sec;
1590     syst->wMilliseconds = remainder / 10000;
1591     return TRUE; 
1592 }
1593
1594 /***********************************************************************
1595  *           QueryDosDeviceA   (KERNEL32.413)
1596  *
1597  * returns array of strings terminated by \0, terminated by \0
1598  */
1599 DWORD WINAPI QueryDosDevice32A(LPCSTR devname,LPSTR target,DWORD bufsize)
1600 {
1601     LPSTR s;
1602     char  buffer[200];
1603
1604     TRACE(dosfs,"(%s,...)\n",devname?devname:"<null>");
1605     if (!devname) {
1606         /* return known MSDOS devices */
1607         lstrcpy32A(buffer,"CON COM1 COM2 LPT1 NUL ");
1608         while ((s=strchr(buffer,' ')))
1609                 *s='\0';
1610
1611         lstrcpyn32A(target,buffer,bufsize);
1612         return strlen(buffer);
1613     }
1614     lstrcpy32A(buffer,"\\DEV\\");
1615     lstrcat32A(buffer,devname);
1616     if ((s=strchr(buffer,':'))) *s='\0';
1617     lstrcpyn32A(target,buffer,bufsize);
1618     return strlen(buffer);
1619 }
1620
1621
1622 /***********************************************************************
1623  *           QueryDosDeviceW   (KERNEL32.414)
1624  *
1625  * returns array of strings terminated by \0, terminated by \0
1626  */
1627 DWORD WINAPI QueryDosDevice32W(LPCWSTR devname,LPWSTR target,DWORD bufsize)
1628 {
1629     LPSTR devnameA = devname?HEAP_strdupWtoA(GetProcessHeap(),0,devname):NULL;
1630     LPSTR targetA = (LPSTR)HEAP_xalloc(GetProcessHeap(),0,bufsize);
1631     DWORD ret = QueryDosDevice32A(devnameA,targetA,bufsize);
1632
1633     lstrcpynAtoW(target,targetA,bufsize);
1634     if (devnameA) HeapFree(GetProcessHeap(),0,devnameA);
1635     if (targetA) HeapFree(GetProcessHeap(),0,targetA);
1636     return ret;
1637 }
1638
1639
1640 /***********************************************************************
1641  *           SystemTimeToFileTime   (KERNEL32.526)
1642  */
1643 BOOL32 WINAPI SystemTimeToFileTime( const SYSTEMTIME *syst, LPFILETIME ft )
1644 {
1645 #ifdef HAVE_TIMEGM
1646     struct tm xtm;
1647     time_t utctime;
1648 #else
1649     struct tm xtm,*local_tm,*utc_tm;
1650     time_t localtim,utctime;
1651 #endif
1652
1653     xtm.tm_year = syst->wYear-1900;
1654     xtm.tm_mon  = syst->wMonth - 1;
1655     xtm.tm_wday = syst->wDayOfWeek;
1656     xtm.tm_mday = syst->wDay;
1657     xtm.tm_hour = syst->wHour;
1658     xtm.tm_min  = syst->wMinute;
1659     xtm.tm_sec  = syst->wSecond; /* this is UTC */
1660     xtm.tm_isdst = -1;
1661 #ifdef HAVE_TIMEGM
1662     utctime = timegm(&xtm);
1663     DOSFS_UnixTimeToFileTime( utctime, ft, 
1664                               syst->wMilliseconds * 10000 );
1665 #else
1666     localtim = mktime(&xtm);    /* now we've got local time */
1667     local_tm = localtime(&localtim);
1668     utc_tm = gmtime(&localtim);
1669     utctime = mktime(utc_tm);
1670     DOSFS_UnixTimeToFileTime( 2*localtim -utctime, ft, 
1671                               syst->wMilliseconds * 10000 );
1672 #endif
1673     return TRUE; 
1674 }