Make function BX=6 round ST0 to integer.
[wine.git] / files / dos_fs.c
blobb88b2cbb2796974c3f151b9d59be6d68764ac26c
1 /*
2 * DOS file system functions
4 * Copyright 1993 Erik Bos
5 * Copyright 1996 Alexandre Julliard
6 */
8 #include "config.h"
9 #include <sys/types.h>
10 #include <ctype.h>
11 #include <dirent.h>
12 #include <errno.h>
13 #include <sys/errno.h>
14 #include <fcntl.h>
15 #include <string.h>
16 #include <stdlib.h>
17 #include <sys/stat.h>
18 #include <sys/ioctl.h>
19 #include <time.h>
20 #include <unistd.h>
22 #include "windef.h"
23 #include "winuser.h"
24 #include "wine/winbase16.h"
25 #include "winerror.h"
26 #include "drive.h"
27 #include "file.h"
28 #include "heap.h"
29 #include "msdos.h"
30 #include "syslevel.h"
31 #include "server.h"
32 #include "process.h"
33 #include "debug.h"
35 /* Define the VFAT ioctl to get both short and long file names */
36 /* FIXME: is it possible to get this to work on other systems? */
37 #ifdef linux
38 #define VFAT_IOCTL_READDIR_BOTH _IOR('r', 1, long)
39 /* We want the real kernel dirent structure, not the libc one */
40 typedef struct
42 long d_ino;
43 long d_off;
44 unsigned short d_reclen;
45 char d_name[256];
46 } KERNEL_DIRENT;
48 #else /* linux */
49 #undef VFAT_IOCTL_READDIR_BOTH /* just in case... */
50 #endif /* linux */
52 /* Chars we don't want to see in DOS file names */
53 #define INVALID_DOS_CHARS "*?<>|\"+=,;[] \345"
55 static const DOS_DEVICE DOSFS_Devices[] =
56 /* name, device flags (see Int 21/AX=0x4400) */
58 { "CON", 0xc0d3 },
59 { "PRN", 0xa0c0 },
60 { "NUL", 0x80c4 },
61 { "AUX", 0x80c0 },
62 { "LPT1", 0xa0c0 },
63 { "LPT2", 0xa0c0 },
64 { "LPT3", 0xa0c0 },
65 { "LPT4", 0xc0d3 },
66 { "COM1", 0x80c0 },
67 { "COM2", 0x80c0 },
68 { "COM3", 0x80c0 },
69 { "COM4", 0x80c0 },
70 { "SCSIMGR$", 0xc0c0 },
71 { "HPSCAN", 0xc0c0 }
74 #define GET_DRIVE(path) \
75 (((path)[1] == ':') ? toupper((path)[0]) - 'A' : DOSFS_CurDrive)
77 /* Directory info for DOSFS_ReadDir */
78 typedef struct
80 DIR *dir;
81 #ifdef VFAT_IOCTL_READDIR_BOTH
82 int fd;
83 char short_name[12];
84 KERNEL_DIRENT dirent[2];
85 #endif
86 } DOS_DIR;
88 /* Info structure for FindFirstFile handle */
89 typedef struct
91 LPSTR path;
92 LPSTR long_mask;
93 LPSTR short_mask;
94 BYTE attr;
95 int drive;
96 int cur_pos;
97 DOS_DIR *dir;
98 } FIND_FIRST_INFO;
102 /***********************************************************************
103 * DOSFS_ValidDOSName
105 * Return 1 if Unix file 'name' is also a valid MS-DOS name
106 * (i.e. contains only valid DOS chars, lower-case only, fits in 8.3 format).
107 * File name can be terminated by '\0', '\\' or '/'.
109 static int DOSFS_ValidDOSName( const char *name, int ignore_case )
111 static const char invalid_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" INVALID_DOS_CHARS;
112 const char *p = name;
113 const char *invalid = ignore_case ? (invalid_chars + 26) : invalid_chars;
114 int len = 0;
116 if (*p == '.')
118 /* Check for "." and ".." */
119 p++;
120 if (*p == '.') p++;
121 /* All other names beginning with '.' are invalid */
122 return (IS_END_OF_NAME(*p));
124 while (!IS_END_OF_NAME(*p))
126 if (strchr( invalid, *p )) return 0; /* Invalid char */
127 if (*p == '.') break; /* Start of the extension */
128 if (++len > 8) return 0; /* Name too long */
129 p++;
131 if (*p != '.') return 1; /* End of name */
132 p++;
133 if (IS_END_OF_NAME(*p)) return 0; /* Empty extension not allowed */
134 len = 0;
135 while (!IS_END_OF_NAME(*p))
137 if (strchr( invalid, *p )) return 0; /* Invalid char */
138 if (*p == '.') return 0; /* Second extension not allowed */
139 if (++len > 3) return 0; /* Extension too long */
140 p++;
142 return 1;
146 /***********************************************************************
147 * DOSFS_ToDosFCBFormat
149 * Convert a file name to DOS FCB format (8+3 chars, padded with blanks),
150 * expanding wild cards and converting to upper-case in the process.
151 * File name can be terminated by '\0', '\\' or '/'.
152 * Return FALSE if the name is not a valid DOS name.
153 * 'buffer' must be at least 12 characters long.
155 BOOL DOSFS_ToDosFCBFormat( LPCSTR name, LPSTR buffer )
157 static const char invalid_chars[] = INVALID_DOS_CHARS;
158 const char *p = name;
159 int i;
161 /* Check for "." and ".." */
162 if (*p == '.')
164 p++;
165 strcpy( buffer, ". " );
166 if (*p == '.')
168 buffer[1] = '.';
169 p++;
171 return (!*p || (*p == '/') || (*p == '\\'));
174 for (i = 0; i < 8; i++)
176 switch(*p)
178 case '\0':
179 case '\\':
180 case '/':
181 case '.':
182 buffer[i] = ' ';
183 break;
184 case '?':
185 p++;
186 /* fall through */
187 case '*':
188 buffer[i] = '?';
189 break;
190 default:
191 if (strchr( invalid_chars, *p )) return FALSE;
192 buffer[i] = toupper(*p);
193 p++;
194 break;
198 if (*p == '*')
200 /* Skip all chars after wildcard up to first dot */
201 while (*p && (*p != '/') && (*p != '\\') && (*p != '.')) p++;
203 else
205 /* Check if name too long */
206 if (*p && (*p != '/') && (*p != '\\') && (*p != '.')) return FALSE;
208 if (*p == '.') p++; /* Skip dot */
210 for (i = 8; i < 11; i++)
212 switch(*p)
214 case '\0':
215 case '\\':
216 case '/':
217 buffer[i] = ' ';
218 break;
219 case '.':
220 return FALSE; /* Second extension not allowed */
221 case '?':
222 p++;
223 /* fall through */
224 case '*':
225 buffer[i] = '?';
226 break;
227 default:
228 if (strchr( invalid_chars, *p )) return FALSE;
229 buffer[i] = toupper(*p);
230 p++;
231 break;
234 buffer[11] = '\0';
235 return TRUE;
239 /***********************************************************************
240 * DOSFS_ToDosDTAFormat
242 * Convert a file name from FCB to DTA format (name.ext, null-terminated)
243 * converting to upper-case in the process.
244 * File name can be terminated by '\0', '\\' or '/'.
245 * 'buffer' must be at least 13 characters long.
247 static void DOSFS_ToDosDTAFormat( LPCSTR name, LPSTR buffer )
249 char *p;
251 memcpy( buffer, name, 8 );
252 for (p = buffer + 8; (p > buffer) && (p[-1] == ' '); p--);
253 *p++ = '.';
254 memcpy( p, name + 8, 3 );
255 for (p += 3; p[-1] == ' '; p--);
256 if (p[-1] == '.') p--;
257 *p = '\0';
261 /***********************************************************************
262 * DOSFS_MatchShort
264 * Check a DOS file name against a mask (both in FCB format).
266 static int DOSFS_MatchShort( const char *mask, const char *name )
268 int i;
269 for (i = 11; i > 0; i--, mask++, name++)
270 if ((*mask != '?') && (*mask != *name)) return 0;
271 return 1;
275 /***********************************************************************
276 * DOSFS_MatchLong
278 * Check a long file name against a mask.
280 static int DOSFS_MatchLong( const char *mask, const char *name,
281 int case_sensitive )
283 if (!strcmp( mask, "*.*" )) return 1;
284 while (*name && *mask)
286 if (*mask == '*')
288 mask++;
289 while (*mask == '*') mask++; /* Skip consecutive '*' */
290 if (!*mask) return 1;
291 if (case_sensitive) while (*name && (*name != *mask)) name++;
292 else while (*name && (toupper(*name) != toupper(*mask))) name++;
293 if (!*name) return 0;
295 else if (*mask != '?')
297 if (case_sensitive)
299 if (*mask != *name) return 0;
301 else if (toupper(*mask) != toupper(*name)) return 0;
303 mask++;
304 name++;
306 return (!*name && !*mask);
310 /***********************************************************************
311 * DOSFS_OpenDir
313 static DOS_DIR *DOSFS_OpenDir( LPCSTR path )
315 DOS_DIR *dir = HeapAlloc( SystemHeap, 0, sizeof(*dir) );
316 if (!dir)
318 SetLastError( ERROR_NOT_ENOUGH_MEMORY );
319 return NULL;
322 /* Treat empty path as root directory. This simplifies path split into
323 directory and mask in several other places */
324 if (!*path) path = "/";
326 #ifdef VFAT_IOCTL_READDIR_BOTH
328 /* Check if the VFAT ioctl is supported on this directory */
330 if ((dir->fd = open( path, O_RDONLY )) != -1)
332 if (ioctl( dir->fd, VFAT_IOCTL_READDIR_BOTH, (long)dir->dirent ) == -1)
334 close( dir->fd );
335 dir->fd = -1;
337 else
339 /* Set the file pointer back at the start of the directory */
340 lseek( dir->fd, 0, SEEK_SET );
341 dir->dir = NULL;
342 return dir;
345 #endif /* VFAT_IOCTL_READDIR_BOTH */
347 /* Now use the standard opendir/readdir interface */
349 if (!(dir->dir = opendir( path )))
351 HeapFree( SystemHeap, 0, dir );
352 return NULL;
354 return dir;
358 /***********************************************************************
359 * DOSFS_CloseDir
361 static void DOSFS_CloseDir( DOS_DIR *dir )
363 #ifdef VFAT_IOCTL_READDIR_BOTH
364 if (dir->fd != -1) close( dir->fd );
365 #endif /* VFAT_IOCTL_READDIR_BOTH */
366 if (dir->dir) closedir( dir->dir );
367 HeapFree( SystemHeap, 0, dir );
371 /***********************************************************************
372 * DOSFS_ReadDir
374 static BOOL DOSFS_ReadDir( DOS_DIR *dir, LPCSTR *long_name,
375 LPCSTR *short_name )
377 struct dirent *dirent;
379 #ifdef VFAT_IOCTL_READDIR_BOTH
380 if (dir->fd != -1)
382 if (ioctl( dir->fd, VFAT_IOCTL_READDIR_BOTH, (long)dir->dirent ) != -1) {
383 if (!dir->dirent[0].d_reclen) return FALSE;
384 if (!DOSFS_ToDosFCBFormat( dir->dirent[0].d_name, dir->short_name ))
385 dir->short_name[0] = '\0';
386 *short_name = dir->short_name;
387 if (dir->dirent[1].d_name[0]) *long_name = dir->dirent[1].d_name;
388 else *long_name = dir->dirent[0].d_name;
389 return TRUE;
392 #endif /* VFAT_IOCTL_READDIR_BOTH */
394 if (!(dirent = readdir( dir->dir ))) return FALSE;
395 *long_name = dirent->d_name;
396 *short_name = NULL;
397 return TRUE;
401 /***********************************************************************
402 * DOSFS_Hash
404 * Transform a Unix file name into a hashed DOS name. If the name is a valid
405 * DOS name, it is converted to upper-case; otherwise it is replaced by a
406 * hashed version that fits in 8.3 format.
407 * File name can be terminated by '\0', '\\' or '/'.
408 * 'buffer' must be at least 13 characters long.
410 static void DOSFS_Hash( LPCSTR name, LPSTR buffer, BOOL dir_format,
411 BOOL ignore_case )
413 static const char invalid_chars[] = INVALID_DOS_CHARS "~.";
414 static const char hash_chars[32] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345";
416 const char *p, *ext;
417 char *dst;
418 unsigned short hash;
419 int i;
421 if (dir_format) strcpy( buffer, " " );
423 if (DOSFS_ValidDOSName( name, ignore_case ))
425 /* Check for '.' and '..' */
426 if (*name == '.')
428 buffer[0] = '.';
429 if (!dir_format) buffer[1] = buffer[2] = '\0';
430 if (name[1] == '.') buffer[1] = '.';
431 return;
434 /* Simply copy the name, converting to uppercase */
436 for (dst = buffer; !IS_END_OF_NAME(*name) && (*name != '.'); name++)
437 *dst++ = toupper(*name);
438 if (*name == '.')
440 if (dir_format) dst = buffer + 8;
441 else *dst++ = '.';
442 for (name++; !IS_END_OF_NAME(*name); name++)
443 *dst++ = toupper(*name);
445 if (!dir_format) *dst = '\0';
446 return;
449 /* Compute the hash code of the file name */
450 /* If you know something about hash functions, feel free to */
451 /* insert a better algorithm here... */
452 if (ignore_case)
454 for (p = name, hash = 0xbeef; !IS_END_OF_NAME(p[1]); p++)
455 hash = (hash<<3) ^ (hash>>5) ^ tolower(*p) ^ (tolower(p[1]) << 8);
456 hash = (hash<<3) ^ (hash>>5) ^ tolower(*p); /* Last character*/
458 else
460 for (p = name, hash = 0xbeef; !IS_END_OF_NAME(p[1]); p++)
461 hash = (hash << 3) ^ (hash >> 5) ^ *p ^ (p[1] << 8);
462 hash = (hash << 3) ^ (hash >> 5) ^ *p; /* Last character */
465 /* Find last dot for start of the extension */
466 for (p = name+1, ext = NULL; !IS_END_OF_NAME(*p); p++)
467 if (*p == '.') ext = p;
468 if (ext && IS_END_OF_NAME(ext[1]))
469 ext = NULL; /* Empty extension ignored */
471 /* Copy first 4 chars, replacing invalid chars with '_' */
472 for (i = 4, p = name, dst = buffer; i > 0; i--, p++)
474 if (IS_END_OF_NAME(*p) || (p == ext)) break;
475 *dst++ = strchr( invalid_chars, *p ) ? '_' : toupper(*p);
477 /* Pad to 5 chars with '~' */
478 while (i-- >= 0) *dst++ = '~';
480 /* Insert hash code converted to 3 ASCII chars */
481 *dst++ = hash_chars[(hash >> 10) & 0x1f];
482 *dst++ = hash_chars[(hash >> 5) & 0x1f];
483 *dst++ = hash_chars[hash & 0x1f];
485 /* Copy the first 3 chars of the extension (if any) */
486 if (ext)
488 if (!dir_format) *dst++ = '.';
489 for (i = 3, ext++; (i > 0) && !IS_END_OF_NAME(*ext); i--, ext++)
490 *dst++ = strchr( invalid_chars, *ext ) ? '_' : toupper(*ext);
492 if (!dir_format) *dst = '\0';
496 /***********************************************************************
497 * DOSFS_FindUnixName
499 * Find the Unix file name in a given directory that corresponds to
500 * a file name (either in Unix or DOS format).
501 * File name can be terminated by '\0', '\\' or '/'.
502 * Return TRUE if OK, FALSE if no file name matches.
504 * 'long_buf' must be at least 'long_len' characters long. If the long name
505 * turns out to be larger than that, the function returns FALSE.
506 * 'short_buf' must be at least 13 characters long.
508 BOOL DOSFS_FindUnixName( LPCSTR path, LPCSTR name, LPSTR long_buf,
509 INT long_len, LPSTR short_buf, BOOL ignore_case)
511 DOS_DIR *dir;
512 LPCSTR long_name, short_name;
513 char dos_name[12], tmp_buf[13];
514 BOOL ret;
516 const char *p = strchr( name, '/' );
517 int len = p ? (int)(p - name) : strlen(name);
518 if ((p = strchr( name, '\\' ))) len = MIN( (int)(p - name), len );
519 if (long_len < len + 1) return FALSE;
521 TRACE(dosfs, "%s,%s\n", path, name );
523 if (!DOSFS_ToDosFCBFormat( name, dos_name )) dos_name[0] = '\0';
525 if (!(dir = DOSFS_OpenDir( path )))
527 WARN(dosfs, "(%s,%s): can't open dir: %s\n",
528 path, name, strerror(errno) );
529 return FALSE;
532 while ((ret = DOSFS_ReadDir( dir, &long_name, &short_name )))
534 /* Check against Unix name */
535 if (len == strlen(long_name))
537 if (!ignore_case)
539 if (!lstrncmpA( long_name, name, len )) break;
541 else
543 if (!lstrncmpiA( long_name, name, len )) break;
546 if (dos_name[0])
548 /* Check against hashed DOS name */
549 if (!short_name)
551 DOSFS_Hash( long_name, tmp_buf, TRUE, ignore_case );
552 short_name = tmp_buf;
554 if (!strcmp( dos_name, short_name )) break;
557 if (ret)
559 if (long_buf) strcpy( long_buf, long_name );
560 if (short_buf)
562 if (short_name)
563 DOSFS_ToDosDTAFormat( short_name, short_buf );
564 else
565 DOSFS_Hash( long_name, short_buf, FALSE, ignore_case );
567 TRACE(dosfs, "(%s,%s) -> %s (%s)\n",
568 path, name, long_name, short_buf ? short_buf : "***");
570 else
571 WARN(dosfs, "'%s' not found in '%s'\n", name, path);
572 DOSFS_CloseDir( dir );
573 return ret;
577 /***********************************************************************
578 * DOSFS_GetDevice
580 * Check if a DOS file name represents a DOS device and return the device.
582 const DOS_DEVICE *DOSFS_GetDevice( const char *name )
584 int i;
585 const char *p;
587 if (!name) return NULL; /* if FILE_DupUnixHandle was used */
588 if (name[0] && (name[1] == ':')) name += 2;
589 if ((p = strrchr( name, '/' ))) name = p + 1;
590 if ((p = strrchr( name, '\\' ))) name = p + 1;
591 for (i = 0; i < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0]); i++)
593 const char *dev = DOSFS_Devices[i].name;
594 if (!lstrncmpiA( dev, name, strlen(dev) ))
596 p = name + strlen( dev );
597 if (!*p || (*p == '.')) return &DOSFS_Devices[i];
600 return NULL;
604 /***********************************************************************
605 * DOSFS_GetDeviceByHandle
607 const DOS_DEVICE *DOSFS_GetDeviceByHandle( HFILE hFile )
609 struct get_file_info_request req;
610 struct get_file_info_reply reply;
612 req.handle = hFile;
613 CLIENT_SendRequest( REQ_GET_FILE_INFO, -1, 1, &req, sizeof(req) );
614 if (!CLIENT_WaitSimpleReply( &reply, sizeof(reply), NULL ) &&
615 (reply.type == FILE_TYPE_UNKNOWN))
617 if ((reply.attr >= 0) &&
618 (reply.attr < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0])))
619 return &DOSFS_Devices[reply.attr];
621 return NULL;
625 /***********************************************************************
626 * DOSFS_OpenDevice
628 * Open a DOS device. This might not map 1:1 into the UNIX device concept.
630 HFILE DOSFS_OpenDevice( const char *name, DWORD access )
632 int i;
633 const char *p;
635 if (!name) return (HFILE)NULL; /* if FILE_DupUnixHandle was used */
636 if (name[0] && (name[1] == ':')) name += 2;
637 if ((p = strrchr( name, '/' ))) name = p + 1;
638 if ((p = strrchr( name, '\\' ))) name = p + 1;
639 for (i = 0; i < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0]); i++)
641 const char *dev = DOSFS_Devices[i].name;
642 if (!lstrncmpiA( dev, name, strlen(dev) ))
644 p = name + strlen( dev );
645 if (!*p || (*p == '.')) {
646 /* got it */
647 if (!strcmp(DOSFS_Devices[i].name,"NUL"))
648 return FILE_CreateFile( "/dev/null", access,
649 FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,
650 OPEN_EXISTING, 0, -1 );
651 if (!strcmp(DOSFS_Devices[i].name,"CON")) {
652 HFILE to_dup;
653 HFILE handle;
654 switch (access & (GENERIC_READ|GENERIC_WRITE)) {
655 case GENERIC_READ:
656 to_dup = GetStdHandle( STD_INPUT_HANDLE );
657 break;
658 case GENERIC_WRITE:
659 to_dup = GetStdHandle( STD_OUTPUT_HANDLE );
660 break;
661 default:
662 FIXME(dosfs,"can't open CON read/write\n");
663 return HFILE_ERROR;
664 break;
666 if (!DuplicateHandle( GetCurrentProcess(), to_dup, GetCurrentProcess(),
667 &handle, 0, FALSE, DUPLICATE_SAME_ACCESS ))
668 handle = HFILE_ERROR;
669 return handle;
671 if (!strcmp(DOSFS_Devices[i].name,"SCSIMGR$") ||
672 !strcmp(DOSFS_Devices[i].name,"HPSCAN"))
674 return FILE_CreateDevice( i, access, NULL );
676 FIXME(dosfs,"device open %s not supported (yet)\n",DOSFS_Devices[i].name);
677 return HFILE_ERROR;
681 return HFILE_ERROR;
685 /***********************************************************************
686 * DOSFS_GetPathDrive
688 * Get the drive specified by a given path name (DOS or Unix format).
690 static int DOSFS_GetPathDrive( const char **name )
692 int drive;
693 const char *p = *name;
695 if (*p && (p[1] == ':'))
697 drive = toupper(*p) - 'A';
698 *name += 2;
700 else if (*p == '/') /* Absolute Unix path? */
702 if ((drive = DRIVE_FindDriveRoot( name )) == -1)
704 MSG("Warning: %s not accessible from a DOS drive\n", *name );
705 /* Assume it really was a DOS name */
706 drive = DRIVE_GetCurrentDrive();
709 else drive = DRIVE_GetCurrentDrive();
711 if (!DRIVE_IsValid(drive))
713 SetLastError( ERROR_INVALID_DRIVE );
714 return -1;
716 return drive;
720 /***********************************************************************
721 * DOSFS_GetFullName
723 * Convert a file name (DOS or mixed DOS/Unix format) to a valid
724 * Unix name / short DOS name pair.
725 * Return FALSE if one of the path components does not exist. The last path
726 * component is only checked if 'check_last' is non-zero.
727 * The buffers pointed to by 'long_buf' and 'short_buf' must be
728 * at least MAX_PATHNAME_LEN long.
730 BOOL DOSFS_GetFullName( LPCSTR name, BOOL check_last, DOS_FULL_NAME *full )
732 BOOL found;
733 UINT flags;
734 char *p_l, *p_s, *root;
736 TRACE(dosfs, "%s (last=%d)\n",
737 name, check_last );
739 if ((full->drive = DOSFS_GetPathDrive( &name )) == -1) return FALSE;
740 flags = DRIVE_GetFlags( full->drive );
742 lstrcpynA( full->long_name, DRIVE_GetRoot( full->drive ),
743 sizeof(full->long_name) );
744 if (full->long_name[1]) root = full->long_name + strlen(full->long_name);
745 else root = full->long_name; /* root directory */
747 strcpy( full->short_name, "A:\\" );
748 full->short_name[0] += full->drive;
750 if ((*name == '\\') || (*name == '/')) /* Absolute path */
752 while ((*name == '\\') || (*name == '/')) name++;
754 else /* Relative path */
756 lstrcpynA( root + 1, DRIVE_GetUnixCwd( full->drive ),
757 sizeof(full->long_name) - (root - full->long_name) - 1 );
758 if (root[1]) *root = '/';
759 lstrcpynA( full->short_name + 3, DRIVE_GetDosCwd( full->drive ),
760 sizeof(full->short_name) - 3 );
763 p_l = full->long_name[1] ? full->long_name + strlen(full->long_name)
764 : full->long_name;
765 p_s = full->short_name[3] ? full->short_name + strlen(full->short_name)
766 : full->short_name + 2;
767 found = TRUE;
769 while (*name && found)
771 /* Check for '.' and '..' */
773 if (*name == '.')
775 if (IS_END_OF_NAME(name[1]))
777 name++;
778 while ((*name == '\\') || (*name == '/')) name++;
779 continue;
781 else if ((name[1] == '.') && IS_END_OF_NAME(name[2]))
783 name += 2;
784 while ((*name == '\\') || (*name == '/')) name++;
785 while ((p_l > root) && (*p_l != '/')) p_l--;
786 while ((p_s > full->short_name + 2) && (*p_s != '\\')) p_s--;
787 *p_l = *p_s = '\0'; /* Remove trailing separator */
788 continue;
792 /* Make sure buffers are large enough */
794 if ((p_s >= full->short_name + sizeof(full->short_name) - 14) ||
795 (p_l >= full->long_name + sizeof(full->long_name) - 1))
797 SetLastError( ERROR_PATH_NOT_FOUND );
798 return FALSE;
801 /* Get the long and short name matching the file name */
803 if ((found = DOSFS_FindUnixName( full->long_name, name, p_l + 1,
804 sizeof(full->long_name) - (p_l - full->long_name) - 1,
805 p_s + 1, !(flags & DRIVE_CASE_SENSITIVE) )))
807 *p_l++ = '/';
808 p_l += strlen(p_l);
809 *p_s++ = '\\';
810 p_s += strlen(p_s);
811 while (!IS_END_OF_NAME(*name)) name++;
813 else if (!check_last)
815 *p_l++ = '/';
816 *p_s++ = '\\';
817 while (!IS_END_OF_NAME(*name) &&
818 (p_s < full->short_name + sizeof(full->short_name) - 1) &&
819 (p_l < full->long_name + sizeof(full->long_name) - 1))
821 *p_s++ = tolower(*name);
822 /* If the drive is case-sensitive we want to create new */
823 /* files in lower-case otherwise we can't reopen them */
824 /* under the same short name. */
825 if (flags & DRIVE_CASE_SENSITIVE) *p_l++ = tolower(*name);
826 else *p_l++ = *name;
827 name++;
829 *p_l = *p_s = '\0';
831 while ((*name == '\\') || (*name == '/')) name++;
834 if (!found)
836 if (check_last)
838 SetLastError( ERROR_FILE_NOT_FOUND );
839 return FALSE;
841 if (*name) /* Not last */
843 SetLastError( ERROR_PATH_NOT_FOUND );
844 return FALSE;
847 if (!full->long_name[0]) strcpy( full->long_name, "/" );
848 if (!full->short_name[2]) strcpy( full->short_name + 2, "\\" );
849 TRACE(dosfs, "returning %s = %s\n",
850 full->long_name, full->short_name );
851 return TRUE;
855 /***********************************************************************
856 * GetShortPathName32A (KERNEL32.271)
858 * NOTES
859 * observed:
860 * longpath=NULL: LastError=ERROR_INVALID_PARAMETER, ret=0
861 * *longpath="" or invalid: LastError=ERROR_BAD_PATHNAME, ret=0
863 DWORD WINAPI GetShortPathNameA( LPCSTR longpath, LPSTR shortpath,
864 DWORD shortlen )
866 DOS_FULL_NAME full_name;
868 if (!longpath)
870 SetLastError(ERROR_INVALID_PARAMETER);
871 return 0;
874 if (!longpath[0])
876 SetLastError(ERROR_BAD_PATHNAME);
877 return 0;
880 /* FIXME: is it correct to always return a fully qualified short path? */
881 if (!DOSFS_GetFullName( longpath, TRUE, &full_name ))
883 SetLastError(ERROR_BAD_PATHNAME);
884 return 0;
886 lstrcpynA( shortpath, full_name.short_name, shortlen );
887 return strlen( full_name.short_name );
891 /***********************************************************************
892 * GetShortPathName32W (KERNEL32.272)
894 DWORD WINAPI GetShortPathNameW( LPCWSTR longpath, LPWSTR shortpath,
895 DWORD shortlen )
897 DOS_FULL_NAME full_name;
898 LPSTR longpathA ;
899 DWORD ret = 0;
901 if (!longpath)
902 { SetLastError(ERROR_INVALID_PARAMETER);
903 return 0;
906 if (!longpath[0])
907 { SetLastError(ERROR_BAD_PATHNAME);
908 return 0;
912 longpathA = HEAP_strdupWtoA( GetProcessHeap(), 0, longpath );
914 /* FIXME: is it correct to always return a fully qualified short path? */
915 if (DOSFS_GetFullName( longpathA, TRUE, &full_name ))
917 ret = strlen( full_name.short_name );
918 lstrcpynAtoW( shortpath, full_name.short_name, shortlen );
921 SetLastError(ERROR_BAD_PATHNAME);
922 HeapFree( GetProcessHeap(), 0, longpathA );
923 return 0;
927 /***********************************************************************
928 * GetLongPathName32A (KERNEL32.xxx)
930 DWORD WINAPI GetLongPathNameA( LPCSTR shortpath, LPSTR longpath,
931 DWORD longlen )
933 DOS_FULL_NAME full_name;
934 char *p;
935 char *longfilename;
936 DWORD shortpathlen;
938 if (!DOSFS_GetFullName( shortpath, TRUE, &full_name )) return 0;
939 lstrcpynA( longpath, full_name.short_name, longlen );
940 /* Do some hackery to get the long filename.
941 * FIXME: Would be better if it returned the
942 * long version of the directories too
944 longfilename = strrchr(full_name.long_name, '/')+1;
945 if (longpath != NULL) {
946 if ((p = strrchr( longpath, '\\' )) != NULL) {
947 p++;
948 longlen -= (p-longpath);
949 lstrcpynA( p, longfilename , longlen);
952 shortpathlen =
953 ((strrchr( full_name.short_name, '\\' ) - full_name.short_name) + 1);
954 return shortpathlen + strlen( longfilename );
958 /***********************************************************************
959 * GetLongPathName32W (KERNEL32.269)
961 DWORD WINAPI GetLongPathNameW( LPCWSTR shortpath, LPWSTR longpath,
962 DWORD longlen )
964 DOS_FULL_NAME full_name;
965 DWORD ret = 0;
966 LPSTR shortpathA = HEAP_strdupWtoA( GetProcessHeap(), 0, shortpath );
968 /* FIXME: is it correct to always return a fully qualified short path? */
969 if (DOSFS_GetFullName( shortpathA, TRUE, &full_name ))
971 ret = strlen( full_name.short_name );
972 lstrcpynAtoW( longpath, full_name.long_name, longlen );
974 HeapFree( GetProcessHeap(), 0, shortpathA );
975 return ret;
979 /***********************************************************************
980 * DOSFS_DoGetFullPathName
982 * Implementation of GetFullPathName32A/W.
984 static DWORD DOSFS_DoGetFullPathName( LPCSTR name, DWORD len, LPSTR result,
985 BOOL unicode )
987 char buffer[MAX_PATHNAME_LEN];
988 int drive;
989 char *p;
990 DWORD ret;
992 /* last possible position for a char != 0 */
993 char *endchar = buffer + sizeof(buffer) - 2;
994 *endchar = '\0';
996 TRACE(dosfs, "converting '%s'\n", name );
998 if (!name || !result || ((drive = DOSFS_GetPathDrive( &name )) == -1) )
999 { SetLastError( ERROR_INVALID_PARAMETER );
1000 return 0;
1003 p = buffer;
1004 *p++ = 'A' + drive;
1005 *p++ = ':';
1006 if (IS_END_OF_NAME(*name) && (*name)) /* Absolute path */
1008 while (((*name == '\\') || (*name == '/')) && (!*endchar) )
1009 *p++ = *name++;
1011 else /* Relative path or empty path */
1013 *p++ = '\\';
1014 lstrcpynA( p, DRIVE_GetDosCwd(drive), sizeof(buffer) - 4 );
1015 if ( *p )
1017 p += strlen(p);
1018 *p++ = '\\';
1021 *p = '\0';
1023 while (*name)
1025 if (*name == '.')
1027 if (IS_END_OF_NAME(name[1]))
1029 name++;
1030 while ((*name == '\\') || (*name == '/')) name++;
1031 continue;
1033 else if ((name[1] == '.') && IS_END_OF_NAME(name[2]))
1035 name += 2;
1036 while ((*name == '\\') || (*name == '/')) name++;
1038 if (p < buffer + 3) /* no previous dir component */
1039 continue;
1040 p--; /* skip previously added '\\' */
1041 while ((*p == '\\') || (*p == '/')) p--;
1042 /* skip previous dir component */
1043 while ((*p != '\\') && (*p != '/')) p--;
1044 p++;
1045 continue;
1048 if ( *endchar )
1049 { SetLastError( ERROR_PATH_NOT_FOUND );
1050 return 0;
1052 while (!IS_END_OF_NAME(*name) && (!*endchar) )
1053 *p++ = *name++;
1054 while (((*name == '\\') || (*name == '/')) && (!*endchar) )
1055 *p++ = *name++;
1057 *p = '\0';
1059 if (!(DRIVE_GetFlags(drive) & DRIVE_CASE_PRESERVING))
1060 CharUpperA( buffer );
1062 if (unicode)
1063 lstrcpynAtoW( (LPWSTR)result, buffer, len );
1064 else
1065 lstrcpynA( result, buffer, len );
1067 TRACE(dosfs, "returning '%s'\n", buffer );
1069 /* If the lpBuffer buffer is too small, the return value is the
1070 size of the buffer, in characters, required to hold the path. */
1072 ret = strlen(buffer);
1074 if (ret >= len )
1075 SetLastError( ERROR_INSUFFICIENT_BUFFER );
1077 return ret;
1081 /***********************************************************************
1082 * GetFullPathName32A (KERNEL32.272)
1083 * NOTES
1084 * if the path closed with '\', *lastpart is 0
1086 DWORD WINAPI GetFullPathNameA( LPCSTR name, DWORD len, LPSTR buffer,
1087 LPSTR *lastpart )
1089 DWORD ret = DOSFS_DoGetFullPathName( name, len, buffer, FALSE );
1090 if (ret && lastpart)
1092 LPSTR p = buffer + strlen(buffer);
1094 if (*p != '\\')
1096 while ((p > buffer + 2) && (*p != '\\')) p--;
1097 *lastpart = p + 1;
1099 else *lastpart = NULL;
1101 return ret;
1105 /***********************************************************************
1106 * GetFullPathName32W (KERNEL32.273)
1108 DWORD WINAPI GetFullPathNameW( LPCWSTR name, DWORD len, LPWSTR buffer,
1109 LPWSTR *lastpart )
1111 LPSTR nameA = HEAP_strdupWtoA( GetProcessHeap(), 0, name );
1112 DWORD ret = DOSFS_DoGetFullPathName( nameA, len, (LPSTR)buffer, TRUE );
1113 HeapFree( GetProcessHeap(), 0, nameA );
1114 if (ret && lastpart)
1116 LPWSTR p = buffer + lstrlenW(buffer);
1117 if (*p != (WCHAR)'\\')
1119 while ((p > buffer + 2) && (*p != (WCHAR)'\\')) p--;
1120 *lastpart = p + 1;
1122 else *lastpart = NULL;
1124 return ret;
1127 /***********************************************************************
1128 * DOSFS_FindNextEx
1130 static int DOSFS_FindNextEx( FIND_FIRST_INFO *info, WIN32_FIND_DATAA *entry )
1132 BYTE attr = info->attr | FA_UNUSED | FA_ARCHIVE | FA_RDONLY;
1133 UINT flags = DRIVE_GetFlags( info->drive );
1134 char *p, buffer[MAX_PATHNAME_LEN];
1135 const char *drive_path;
1136 int drive_root;
1137 LPCSTR long_name, short_name;
1138 BY_HANDLE_FILE_INFORMATION fileinfo;
1139 char dos_name[13];
1141 if ((info->attr & ~(FA_UNUSED | FA_ARCHIVE | FA_RDONLY)) == FA_LABEL)
1143 if (info->cur_pos) return 0;
1144 entry->dwFileAttributes = FILE_ATTRIBUTE_LABEL;
1145 DOSFS_UnixTimeToFileTime( (time_t)0, &entry->ftCreationTime, 0 );
1146 DOSFS_UnixTimeToFileTime( (time_t)0, &entry->ftLastAccessTime, 0 );
1147 DOSFS_UnixTimeToFileTime( (time_t)0, &entry->ftLastWriteTime, 0 );
1148 entry->nFileSizeHigh = 0;
1149 entry->nFileSizeLow = 0;
1150 entry->dwReserved0 = 0;
1151 entry->dwReserved1 = 0;
1152 DOSFS_ToDosDTAFormat( DRIVE_GetLabel( info->drive ), entry->cFileName );
1153 strcpy( entry->cAlternateFileName, entry->cFileName );
1154 info->cur_pos++;
1155 return 1;
1158 drive_path = info->path + strlen(DRIVE_GetRoot( info->drive ));
1159 while ((*drive_path == '/') || (*drive_path == '\\')) drive_path++;
1160 drive_root = !*drive_path;
1162 lstrcpynA( buffer, info->path, sizeof(buffer) - 1 );
1163 strcat( buffer, "/" );
1164 p = buffer + strlen(buffer);
1166 while (DOSFS_ReadDir( info->dir, &long_name, &short_name ))
1168 info->cur_pos++;
1170 /* Don't return '.' and '..' in the root of the drive */
1171 if (drive_root && (long_name[0] == '.') &&
1172 (!long_name[1] || ((long_name[1] == '.') && !long_name[2])))
1173 continue;
1175 /* Check the long mask */
1177 if (info->long_mask)
1179 if (!DOSFS_MatchLong( info->long_mask, long_name,
1180 flags & DRIVE_CASE_SENSITIVE )) continue;
1183 /* Check the short mask */
1185 if (info->short_mask)
1187 if (!short_name)
1189 DOSFS_Hash( long_name, dos_name, TRUE,
1190 !(flags & DRIVE_CASE_SENSITIVE) );
1191 short_name = dos_name;
1193 if (!DOSFS_MatchShort( info->short_mask, short_name )) continue;
1196 /* Check the file attributes */
1198 lstrcpynA( p, long_name, sizeof(buffer) - (int)(p - buffer) );
1199 if (!FILE_Stat( buffer, &fileinfo ))
1201 WARN(dosfs, "can't stat %s\n", buffer);
1202 continue;
1204 if (fileinfo.dwFileAttributes & ~attr) continue;
1206 /* We now have a matching entry; fill the result and return */
1208 entry->dwFileAttributes = fileinfo.dwFileAttributes;
1209 entry->ftCreationTime = fileinfo.ftCreationTime;
1210 entry->ftLastAccessTime = fileinfo.ftLastAccessTime;
1211 entry->ftLastWriteTime = fileinfo.ftLastWriteTime;
1212 entry->nFileSizeHigh = fileinfo.nFileSizeHigh;
1213 entry->nFileSizeLow = fileinfo.nFileSizeLow;
1215 if (short_name)
1216 DOSFS_ToDosDTAFormat( short_name, entry->cAlternateFileName );
1217 else
1218 DOSFS_Hash( long_name, entry->cAlternateFileName, FALSE,
1219 !(flags & DRIVE_CASE_SENSITIVE) );
1221 lstrcpynA( entry->cFileName, long_name, sizeof(entry->cFileName) );
1222 if (!(flags & DRIVE_CASE_PRESERVING)) CharLowerA( entry->cFileName );
1223 TRACE(dosfs, "returning %s (%s) %02lx %ld\n",
1224 entry->cFileName, entry->cAlternateFileName,
1225 entry->dwFileAttributes, entry->nFileSizeLow );
1226 return 1;
1228 return 0; /* End of directory */
1231 /***********************************************************************
1232 * DOSFS_FindNext
1234 * Find the next matching file. Return the number of entries read to find
1235 * the matching one, or 0 if no more entries.
1236 * 'short_mask' is the 8.3 mask (in FCB format), 'long_mask' is the long
1237 * file name mask. Either or both can be NULL.
1239 * NOTE: This is supposed to be only called by the int21 emulation
1240 * routines. Thus, we should own the Win16Mutex anyway.
1241 * Nevertheless, we explicitly enter it to ensure the static
1242 * directory cache is protected.
1244 int DOSFS_FindNext( const char *path, const char *short_mask,
1245 const char *long_mask, int drive, BYTE attr,
1246 int skip, WIN32_FIND_DATAA *entry )
1248 static FIND_FIRST_INFO info = { NULL };
1249 LPCSTR short_name, long_name;
1250 int count;
1252 SYSLEVEL_EnterWin16Lock();
1254 /* Check the cached directory */
1255 if (!(info.dir && info.path == path && info.short_mask == short_mask
1256 && info.long_mask == long_mask && info.drive == drive
1257 && info.attr == attr && info.cur_pos <= skip))
1259 /* Not in the cache, open it anew */
1260 if (info.dir) DOSFS_CloseDir( info.dir );
1262 info.path = (LPSTR)path;
1263 info.long_mask = (LPSTR)long_mask;
1264 info.short_mask = (LPSTR)short_mask;
1265 info.attr = attr;
1266 info.drive = drive;
1267 info.cur_pos = 0;
1268 info.dir = DOSFS_OpenDir( info.path );
1271 /* Skip to desired position */
1272 while (info.cur_pos < skip)
1273 if (info.dir && DOSFS_ReadDir( info.dir, &long_name, &short_name ))
1274 info.cur_pos++;
1275 else
1276 break;
1278 if (info.dir && info.cur_pos == skip && DOSFS_FindNextEx( &info, entry ))
1279 count = info.cur_pos - skip;
1280 else
1281 count = 0;
1283 if (!count)
1285 if (info.dir) DOSFS_CloseDir( info.dir );
1286 memset( &info, '\0', sizeof(info) );
1289 SYSLEVEL_LeaveWin16Lock();
1291 return count;
1296 /*************************************************************************
1297 * FindFirstFile16 (KERNEL.413)
1299 HANDLE16 WINAPI FindFirstFile16( LPCSTR path, WIN32_FIND_DATAA *data )
1301 DOS_FULL_NAME full_name;
1302 HGLOBAL16 handle;
1303 FIND_FIRST_INFO *info;
1305 data->dwReserved0 = data->dwReserved1 = 0x0;
1306 if (!path) return 0;
1307 if (!DOSFS_GetFullName( path, FALSE, &full_name ))
1308 return INVALID_HANDLE_VALUE16;
1309 if (!(handle = GlobalAlloc16( GMEM_MOVEABLE, sizeof(FIND_FIRST_INFO) )))
1310 return INVALID_HANDLE_VALUE16;
1311 info = (FIND_FIRST_INFO *)GlobalLock16( handle );
1312 info->path = HEAP_strdupA( SystemHeap, 0, full_name.long_name );
1313 info->long_mask = strrchr( info->path, '/' );
1314 *(info->long_mask++) = '\0';
1315 info->short_mask = NULL;
1316 info->attr = 0xff;
1317 if (path[0] && (path[1] == ':')) info->drive = toupper(*path) - 'A';
1318 else info->drive = DRIVE_GetCurrentDrive();
1319 info->cur_pos = 0;
1321 info->dir = DOSFS_OpenDir( info->path );
1323 GlobalUnlock16( handle );
1324 if (!FindNextFile16( handle, data ))
1326 FindClose16( handle );
1327 SetLastError( ERROR_NO_MORE_FILES );
1328 return INVALID_HANDLE_VALUE16;
1330 return handle;
1334 /*************************************************************************
1335 * FindFirstFile32A (KERNEL32.123)
1337 HANDLE WINAPI FindFirstFileA( LPCSTR path, WIN32_FIND_DATAA *data )
1339 HANDLE handle = FindFirstFile16( path, data );
1340 if (handle == INVALID_HANDLE_VALUE16) return INVALID_HANDLE_VALUE;
1341 return handle;
1345 /*************************************************************************
1346 * FindFirstFile32W (KERNEL32.124)
1348 HANDLE WINAPI FindFirstFileW( LPCWSTR path, WIN32_FIND_DATAW *data )
1350 WIN32_FIND_DATAA dataA;
1351 LPSTR pathA = HEAP_strdupWtoA( GetProcessHeap(), 0, path );
1352 HANDLE handle = FindFirstFileA( pathA, &dataA );
1353 HeapFree( GetProcessHeap(), 0, pathA );
1354 if (handle != INVALID_HANDLE_VALUE)
1356 data->dwFileAttributes = dataA.dwFileAttributes;
1357 data->ftCreationTime = dataA.ftCreationTime;
1358 data->ftLastAccessTime = dataA.ftLastAccessTime;
1359 data->ftLastWriteTime = dataA.ftLastWriteTime;
1360 data->nFileSizeHigh = dataA.nFileSizeHigh;
1361 data->nFileSizeLow = dataA.nFileSizeLow;
1362 lstrcpyAtoW( data->cFileName, dataA.cFileName );
1363 lstrcpyAtoW( data->cAlternateFileName, dataA.cAlternateFileName );
1365 return handle;
1369 /*************************************************************************
1370 * FindNextFile16 (KERNEL.414)
1372 BOOL16 WINAPI FindNextFile16( HANDLE16 handle, WIN32_FIND_DATAA *data )
1374 FIND_FIRST_INFO *info;
1376 if (!(info = (FIND_FIRST_INFO *)GlobalLock16( handle )))
1378 SetLastError( ERROR_INVALID_HANDLE );
1379 return FALSE;
1381 GlobalUnlock16( handle );
1382 if (!info->path || !info->dir)
1384 SetLastError( ERROR_NO_MORE_FILES );
1385 return FALSE;
1387 if (!DOSFS_FindNextEx( info, data ))
1389 DOSFS_CloseDir( info->dir ); info->dir = NULL;
1390 HeapFree( SystemHeap, 0, info->path );
1391 info->path = info->long_mask = NULL;
1392 SetLastError( ERROR_NO_MORE_FILES );
1393 return FALSE;
1395 return TRUE;
1399 /*************************************************************************
1400 * FindNextFile32A (KERNEL32.126)
1402 BOOL WINAPI FindNextFileA( HANDLE handle, WIN32_FIND_DATAA *data )
1404 return FindNextFile16( handle, data );
1408 /*************************************************************************
1409 * FindNextFile32W (KERNEL32.127)
1411 BOOL WINAPI FindNextFileW( HANDLE handle, WIN32_FIND_DATAW *data )
1413 WIN32_FIND_DATAA dataA;
1414 if (!FindNextFileA( handle, &dataA )) return FALSE;
1415 data->dwFileAttributes = dataA.dwFileAttributes;
1416 data->ftCreationTime = dataA.ftCreationTime;
1417 data->ftLastAccessTime = dataA.ftLastAccessTime;
1418 data->ftLastWriteTime = dataA.ftLastWriteTime;
1419 data->nFileSizeHigh = dataA.nFileSizeHigh;
1420 data->nFileSizeLow = dataA.nFileSizeLow;
1421 lstrcpyAtoW( data->cFileName, dataA.cFileName );
1422 lstrcpyAtoW( data->cAlternateFileName, dataA.cAlternateFileName );
1423 return TRUE;
1427 /*************************************************************************
1428 * FindClose16 (KERNEL.415)
1430 BOOL16 WINAPI FindClose16( HANDLE16 handle )
1432 FIND_FIRST_INFO *info;
1434 if ((handle == INVALID_HANDLE_VALUE16) ||
1435 !(info = (FIND_FIRST_INFO *)GlobalLock16( handle )))
1437 SetLastError( ERROR_INVALID_HANDLE );
1438 return FALSE;
1440 if (info->dir) DOSFS_CloseDir( info->dir );
1441 if (info->path) HeapFree( SystemHeap, 0, info->path );
1442 GlobalUnlock16( handle );
1443 GlobalFree16( handle );
1444 return TRUE;
1448 /*************************************************************************
1449 * FindClose32 (KERNEL32.119)
1451 BOOL WINAPI FindClose( HANDLE handle )
1453 return FindClose16( (HANDLE16)handle );
1457 /***********************************************************************
1458 * DOSFS_UnixTimeToFileTime
1460 * Convert a Unix time to FILETIME format.
1461 * The FILETIME structure is a 64-bit value representing the number of
1462 * 100-nanosecond intervals since January 1, 1601, 0:00.
1463 * 'remainder' is the nonnegative number of 100-ns intervals
1464 * corresponding to the time fraction smaller than 1 second that
1465 * couldn't be stored in the time_t value.
1467 void DOSFS_UnixTimeToFileTime( time_t unix_time, FILETIME *filetime,
1468 DWORD remainder )
1470 /* NOTES:
1472 CONSTANTS:
1473 The time difference between 1 January 1601, 00:00:00 and
1474 1 January 1970, 00:00:00 is 369 years, plus the leap years
1475 from 1604 to 1968, excluding 1700, 1800, 1900.
1476 This makes (1968 - 1600) / 4 - 3 = 89 leap days, and a total
1477 of 134774 days.
1479 Any day in that period had 24 * 60 * 60 = 86400 seconds.
1481 The time difference is 134774 * 86400 * 10000000, which can be written
1482 116444736000000000
1483 27111902 * 2^32 + 3577643008
1484 413 * 2^48 + 45534 * 2^32 + 54590 * 2^16 + 32768
1486 If you find that these constants are buggy, please change them in all
1487 instances in both conversion functions.
1489 VERSIONS:
1490 There are two versions, one of them uses long long variables and
1491 is presumably faster but not ISO C. The other one uses standard C
1492 data types and operations but relies on the assumption that negative
1493 numbers are stored as 2's complement (-1 is 0xffff....). If this
1494 assumption is violated, dates before 1970 will not convert correctly.
1495 This should however work on any reasonable architecture where WINE
1496 will run.
1498 DETAILS:
1500 Take care not to remove the casts. I have tested these functions
1501 (in both versions) for a lot of numbers. I would be interested in
1502 results on other compilers than GCC.
1504 The operations have been designed to account for the possibility
1505 of 64-bit time_t in future UNICES. Even the versions without
1506 internal long long numbers will work if time_t only is 64 bit.
1507 A 32-bit shift, which was necessary for that operation, turned out
1508 not to work correctly in GCC, besides giving the warning. So I
1509 used a double 16-bit shift instead. Numbers are in the ISO version
1510 represented by three limbs, the most significant with 32 bit, the
1511 other two with 16 bit each.
1513 As the modulo-operator % is not well-defined for negative numbers,
1514 negative divisors have been avoided in DOSFS_FileTimeToUnixTime.
1516 There might be quicker ways to do this in C. Certainly so in
1517 assembler.
1519 Claus Fischer, fischer@iue.tuwien.ac.at
1522 #if (SIZEOF_LONG_LONG >= 8)
1523 # define USE_LONG_LONG 1
1524 #else
1525 # define USE_LONG_LONG 0
1526 #endif
1528 #if USE_LONG_LONG /* gcc supports long long type */
1530 long long int t = unix_time;
1531 t *= 10000000;
1532 t += 116444736000000000LL;
1533 t += remainder;
1534 filetime->dwLowDateTime = (UINT)t;
1535 filetime->dwHighDateTime = (UINT)(t >> 32);
1537 #else /* ISO version */
1539 UINT a0; /* 16 bit, low bits */
1540 UINT a1; /* 16 bit, medium bits */
1541 UINT a2; /* 32 bit, high bits */
1543 /* Copy the unix time to a2/a1/a0 */
1544 a0 = unix_time & 0xffff;
1545 a1 = (unix_time >> 16) & 0xffff;
1546 /* This is obsolete if unix_time is only 32 bits, but it does not hurt.
1547 Do not replace this by >> 32, it gives a compiler warning and it does
1548 not work. */
1549 a2 = (unix_time >= 0 ? (unix_time >> 16) >> 16 :
1550 ~((~unix_time >> 16) >> 16));
1552 /* Multiply a by 10000000 (a = a2/a1/a0)
1553 Split the factor into 10000 * 1000 which are both less than 0xffff. */
1554 a0 *= 10000;
1555 a1 = a1 * 10000 + (a0 >> 16);
1556 a2 = a2 * 10000 + (a1 >> 16);
1557 a0 &= 0xffff;
1558 a1 &= 0xffff;
1560 a0 *= 1000;
1561 a1 = a1 * 1000 + (a0 >> 16);
1562 a2 = a2 * 1000 + (a1 >> 16);
1563 a0 &= 0xffff;
1564 a1 &= 0xffff;
1566 /* Add the time difference and the remainder */
1567 a0 += 32768 + (remainder & 0xffff);
1568 a1 += 54590 + (remainder >> 16 ) + (a0 >> 16);
1569 a2 += 27111902 + (a1 >> 16);
1570 a0 &= 0xffff;
1571 a1 &= 0xffff;
1573 /* Set filetime */
1574 filetime->dwLowDateTime = (a1 << 16) + a0;
1575 filetime->dwHighDateTime = a2;
1576 #endif
1580 /***********************************************************************
1581 * DOSFS_FileTimeToUnixTime
1583 * Convert a FILETIME format to Unix time.
1584 * If not NULL, 'remainder' contains the fractional part of the filetime,
1585 * in the range of [0..9999999] (even if time_t is negative).
1587 time_t DOSFS_FileTimeToUnixTime( const FILETIME *filetime, DWORD *remainder )
1589 /* Read the comment in the function DOSFS_UnixTimeToFileTime. */
1590 #if USE_LONG_LONG
1592 long long int t = filetime->dwHighDateTime;
1593 t <<= 32;
1594 t += (UINT)filetime->dwLowDateTime;
1595 t -= 116444736000000000LL;
1596 if (t < 0)
1598 if (remainder) *remainder = 9999999 - (-t - 1) % 10000000;
1599 return -1 - ((-t - 1) / 10000000);
1601 else
1603 if (remainder) *remainder = t % 10000000;
1604 return t / 10000000;
1607 #else /* ISO version */
1609 UINT a0; /* 16 bit, low bits */
1610 UINT a1; /* 16 bit, medium bits */
1611 UINT a2; /* 32 bit, high bits */
1612 UINT r; /* remainder of division */
1613 unsigned int carry; /* carry bit for subtraction */
1614 int negative; /* whether a represents a negative value */
1616 /* Copy the time values to a2/a1/a0 */
1617 a2 = (UINT)filetime->dwHighDateTime;
1618 a1 = ((UINT)filetime->dwLowDateTime ) >> 16;
1619 a0 = ((UINT)filetime->dwLowDateTime ) & 0xffff;
1621 /* Subtract the time difference */
1622 if (a0 >= 32768 ) a0 -= 32768 , carry = 0;
1623 else a0 += (1 << 16) - 32768 , carry = 1;
1625 if (a1 >= 54590 + carry) a1 -= 54590 + carry, carry = 0;
1626 else a1 += (1 << 16) - 54590 - carry, carry = 1;
1628 a2 -= 27111902 + carry;
1630 /* If a is negative, replace a by (-1-a) */
1631 negative = (a2 >= ((UINT)1) << 31);
1632 if (negative)
1634 /* Set a to -a - 1 (a is a2/a1/a0) */
1635 a0 = 0xffff - a0;
1636 a1 = 0xffff - a1;
1637 a2 = ~a2;
1640 /* Divide a by 10000000 (a = a2/a1/a0), put the rest into r.
1641 Split the divisor into 10000 * 1000 which are both less than 0xffff. */
1642 a1 += (a2 % 10000) << 16;
1643 a2 /= 10000;
1644 a0 += (a1 % 10000) << 16;
1645 a1 /= 10000;
1646 r = a0 % 10000;
1647 a0 /= 10000;
1649 a1 += (a2 % 1000) << 16;
1650 a2 /= 1000;
1651 a0 += (a1 % 1000) << 16;
1652 a1 /= 1000;
1653 r += (a0 % 1000) * 10000;
1654 a0 /= 1000;
1656 /* If a was negative, replace a by (-1-a) and r by (9999999 - r) */
1657 if (negative)
1659 /* Set a to -a - 1 (a is a2/a1/a0) */
1660 a0 = 0xffff - a0;
1661 a1 = 0xffff - a1;
1662 a2 = ~a2;
1664 r = 9999999 - r;
1667 if (remainder) *remainder = r;
1669 /* Do not replace this by << 32, it gives a compiler warning and it does
1670 not work. */
1671 return ((((time_t)a2) << 16) << 16) + (a1 << 16) + a0;
1672 #endif
1676 /***********************************************************************
1677 * DosDateTimeToFileTime (KERNEL32.76)
1679 BOOL WINAPI DosDateTimeToFileTime( WORD fatdate, WORD fattime, LPFILETIME ft)
1681 struct tm newtm;
1683 newtm.tm_sec = (fattime & 0x1f) * 2;
1684 newtm.tm_min = (fattime >> 5) & 0x3f;
1685 newtm.tm_hour = (fattime >> 11);
1686 newtm.tm_mday = (fatdate & 0x1f);
1687 newtm.tm_mon = ((fatdate >> 5) & 0x0f) - 1;
1688 newtm.tm_year = (fatdate >> 9) + 80;
1689 DOSFS_UnixTimeToFileTime( mktime( &newtm ), ft, 0 );
1690 return TRUE;
1694 /***********************************************************************
1695 * FileTimeToDosDateTime (KERNEL32.111)
1697 BOOL WINAPI FileTimeToDosDateTime( const FILETIME *ft, LPWORD fatdate,
1698 LPWORD fattime )
1700 time_t unixtime = DOSFS_FileTimeToUnixTime( ft, NULL );
1701 struct tm *tm = localtime( &unixtime );
1702 if (fattime)
1703 *fattime = (tm->tm_hour << 11) + (tm->tm_min << 5) + (tm->tm_sec / 2);
1704 if (fatdate)
1705 *fatdate = ((tm->tm_year - 80) << 9) + ((tm->tm_mon + 1) << 5)
1706 + tm->tm_mday;
1707 return TRUE;
1711 /***********************************************************************
1712 * LocalFileTimeToFileTime (KERNEL32.373)
1714 BOOL WINAPI LocalFileTimeToFileTime( const FILETIME *localft,
1715 LPFILETIME utcft )
1717 struct tm *xtm;
1718 DWORD remainder;
1720 /* convert from local to UTC. Perhaps not correct. FIXME */
1721 time_t unixtime = DOSFS_FileTimeToUnixTime( localft, &remainder );
1722 xtm = gmtime( &unixtime );
1723 DOSFS_UnixTimeToFileTime( mktime(xtm), utcft, remainder );
1724 return TRUE;
1728 /***********************************************************************
1729 * FileTimeToLocalFileTime (KERNEL32.112)
1731 BOOL WINAPI FileTimeToLocalFileTime( const FILETIME *utcft,
1732 LPFILETIME localft )
1734 DWORD remainder;
1735 /* convert from UTC to local. Perhaps not correct. FIXME */
1736 time_t unixtime = DOSFS_FileTimeToUnixTime( utcft, &remainder );
1737 #ifdef HAVE_TIMEGM
1738 struct tm *xtm = localtime( &unixtime );
1739 time_t localtime;
1741 localtime = timegm(xtm);
1742 DOSFS_UnixTimeToFileTime( localtime, localft, remainder );
1744 #else
1745 struct tm *xtm,*gtm;
1746 time_t time1,time2;
1748 xtm = localtime( &unixtime );
1749 gtm = gmtime( &unixtime );
1750 time1 = mktime(xtm);
1751 time2 = mktime(gtm);
1752 DOSFS_UnixTimeToFileTime( 2*time1-time2, localft, remainder );
1753 #endif
1754 return TRUE;
1758 /***********************************************************************
1759 * FileTimeToSystemTime (KERNEL32.113)
1761 BOOL WINAPI FileTimeToSystemTime( const FILETIME *ft, LPSYSTEMTIME syst )
1763 struct tm *xtm;
1764 DWORD remainder;
1765 time_t xtime = DOSFS_FileTimeToUnixTime( ft, &remainder );
1766 xtm = gmtime(&xtime);
1767 syst->wYear = xtm->tm_year+1900;
1768 syst->wMonth = xtm->tm_mon + 1;
1769 syst->wDayOfWeek = xtm->tm_wday;
1770 syst->wDay = xtm->tm_mday;
1771 syst->wHour = xtm->tm_hour;
1772 syst->wMinute = xtm->tm_min;
1773 syst->wSecond = xtm->tm_sec;
1774 syst->wMilliseconds = remainder / 10000;
1775 return TRUE;
1778 /***********************************************************************
1779 * QueryDosDeviceA (KERNEL32.413)
1781 * returns array of strings terminated by \0, terminated by \0
1783 DWORD WINAPI QueryDosDeviceA(LPCSTR devname,LPSTR target,DWORD bufsize)
1785 LPSTR s;
1786 char buffer[200];
1788 TRACE(dosfs,"(%s,...)\n",devname?devname:"<null>");
1789 if (!devname) {
1790 /* return known MSDOS devices */
1791 lstrcpyA(buffer,"CON COM1 COM2 LPT1 NUL ");
1792 while ((s=strchr(buffer,' ')))
1793 *s='\0';
1795 lstrcpynA(target,buffer,bufsize);
1796 return strlen(buffer);
1798 lstrcpyA(buffer,"\\DEV\\");
1799 lstrcatA(buffer,devname);
1800 if ((s=strchr(buffer,':'))) *s='\0';
1801 lstrcpynA(target,buffer,bufsize);
1802 return strlen(buffer);
1806 /***********************************************************************
1807 * QueryDosDeviceW (KERNEL32.414)
1809 * returns array of strings terminated by \0, terminated by \0
1811 DWORD WINAPI QueryDosDeviceW(LPCWSTR devname,LPWSTR target,DWORD bufsize)
1813 LPSTR devnameA = devname?HEAP_strdupWtoA(GetProcessHeap(),0,devname):NULL;
1814 LPSTR targetA = (LPSTR)HEAP_xalloc(GetProcessHeap(),0,bufsize);
1815 DWORD ret = QueryDosDeviceA(devnameA,targetA,bufsize);
1817 lstrcpynAtoW(target,targetA,bufsize);
1818 if (devnameA) HeapFree(GetProcessHeap(),0,devnameA);
1819 if (targetA) HeapFree(GetProcessHeap(),0,targetA);
1820 return ret;
1824 /***********************************************************************
1825 * SystemTimeToFileTime (KERNEL32.526)
1827 BOOL WINAPI SystemTimeToFileTime( const SYSTEMTIME *syst, LPFILETIME ft )
1829 #ifdef HAVE_TIMEGM
1830 struct tm xtm;
1831 time_t utctime;
1832 #else
1833 struct tm xtm,*local_tm,*utc_tm;
1834 time_t localtim,utctime;
1835 #endif
1837 xtm.tm_year = syst->wYear-1900;
1838 xtm.tm_mon = syst->wMonth - 1;
1839 xtm.tm_wday = syst->wDayOfWeek;
1840 xtm.tm_mday = syst->wDay;
1841 xtm.tm_hour = syst->wHour;
1842 xtm.tm_min = syst->wMinute;
1843 xtm.tm_sec = syst->wSecond; /* this is UTC */
1844 xtm.tm_isdst = -1;
1845 #ifdef HAVE_TIMEGM
1846 utctime = timegm(&xtm);
1847 DOSFS_UnixTimeToFileTime( utctime, ft,
1848 syst->wMilliseconds * 10000 );
1849 #else
1850 localtim = mktime(&xtm); /* now we've got local time */
1851 local_tm = localtime(&localtim);
1852 utc_tm = gmtime(&localtim);
1853 utctime = mktime(utc_tm);
1854 DOSFS_UnixTimeToFileTime( 2*localtim -utctime, ft,
1855 syst->wMilliseconds * 10000 );
1856 #endif
1857 return TRUE;
1860 BOOL WINAPI DefineDosDeviceA(DWORD flags,LPCSTR devname,LPCSTR targetpath) {
1861 FIXME(dosfs,"(0x%08lx,%s,%s),stub!\n",flags,devname,targetpath);
1862 SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
1863 return FALSE;