Bugfix for trailing slashes, repetition of '/' or '\' and
[wine/multimedia.git] / files / dos_fs.c
blob2bc9be3811a64a25ddb0385b9f652558537efa24
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 "windows.h"
23 #include "winerror.h"
24 #include "drive.h"
25 #include "file.h"
26 #include "heap.h"
27 #include "msdos.h"
28 #include "syslevel.h"
29 #include "debug.h"
31 /* Define the VFAT ioctl to get both short and long file names */
32 /* FIXME: is it possible to get this to work on other systems? */
33 #ifdef linux
34 #define VFAT_IOCTL_READDIR_BOTH _IOR('r', 1, long)
35 /* We want the real kernel dirent structure, not the libc one */
36 typedef struct
38 long d_ino;
39 long d_off;
40 unsigned short d_reclen;
41 char d_name[256];
42 } KERNEL_DIRENT;
44 #else /* linux */
45 #undef VFAT_IOCTL_READDIR_BOTH /* just in case... */
46 #endif /* linux */
48 /* Chars we don't want to see in DOS file names */
49 #define INVALID_DOS_CHARS "*?<>|\"+=,;[] \345"
51 static const DOS_DEVICE DOSFS_Devices[] =
52 /* name, device flags (see Int 21/AX=0x4400) */
54 { "CON", 0xc0d3 },
55 { "PRN", 0xa0c0 },
56 { "NUL", 0x80c4 },
57 { "AUX", 0x80c0 },
58 { "LPT1", 0xa0c0 },
59 { "LPT2", 0xa0c0 },
60 { "LPT3", 0xa0c0 },
61 { "LPT4", 0xc0d3 },
62 { "COM1", 0x80c0 },
63 { "COM2", 0x80c0 },
64 { "COM3", 0x80c0 },
65 { "COM4", 0x80c0 },
66 { "SCSIMGR$", 0xc0c0 },
67 { "HPSCAN", 0xc0c0 }
70 #define GET_DRIVE(path) \
71 (((path)[1] == ':') ? toupper((path)[0]) - 'A' : DOSFS_CurDrive)
73 /* DOS extended error status */
74 WORD DOS_ExtendedError;
75 BYTE DOS_ErrorClass;
76 BYTE DOS_ErrorAction;
77 BYTE DOS_ErrorLocus;
79 /* Directory info for DOSFS_ReadDir */
80 typedef struct
82 DIR *dir;
83 #ifdef VFAT_IOCTL_READDIR_BOTH
84 int fd;
85 char short_name[12];
86 KERNEL_DIRENT dirent[2];
87 #endif
88 } DOS_DIR;
90 /* Info structure for FindFirstFile handle */
91 typedef struct
93 LPSTR path;
94 LPSTR long_mask;
95 LPSTR short_mask;
96 BYTE attr;
97 int drive;
98 int cur_pos;
99 DOS_DIR *dir;
100 } FIND_FIRST_INFO;
104 /***********************************************************************
105 * DOSFS_ValidDOSName
107 * Return 1 if Unix file 'name' is also a valid MS-DOS name
108 * (i.e. contains only valid DOS chars, lower-case only, fits in 8.3 format).
109 * File name can be terminated by '\0', '\\' or '/'.
111 static int DOSFS_ValidDOSName( const char *name, int ignore_case )
113 static const char invalid_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" INVALID_DOS_CHARS;
114 const char *p = name;
115 const char *invalid = ignore_case ? (invalid_chars + 26) : invalid_chars;
116 int len = 0;
118 if (*p == '.')
120 /* Check for "." and ".." */
121 p++;
122 if (*p == '.') p++;
123 /* All other names beginning with '.' are invalid */
124 return (IS_END_OF_NAME(*p));
126 while (!IS_END_OF_NAME(*p))
128 if (strchr( invalid, *p )) return 0; /* Invalid char */
129 if (*p == '.') break; /* Start of the extension */
130 if (++len > 8) return 0; /* Name too long */
131 p++;
133 if (*p != '.') return 1; /* End of name */
134 p++;
135 if (IS_END_OF_NAME(*p)) return 0; /* Empty extension not allowed */
136 len = 0;
137 while (!IS_END_OF_NAME(*p))
139 if (strchr( invalid, *p )) return 0; /* Invalid char */
140 if (*p == '.') return 0; /* Second extension not allowed */
141 if (++len > 3) return 0; /* Extension too long */
142 p++;
144 return 1;
148 /***********************************************************************
149 * DOSFS_ToDosFCBFormat
151 * Convert a file name to DOS FCB format (8+3 chars, padded with blanks),
152 * expanding wild cards and converting to upper-case in the process.
153 * File name can be terminated by '\0', '\\' or '/'.
154 * Return FALSE if the name is not a valid DOS name.
155 * 'buffer' must be at least 12 characters long.
157 BOOL32 DOSFS_ToDosFCBFormat( LPCSTR name, LPSTR buffer )
159 static const char invalid_chars[] = INVALID_DOS_CHARS;
160 const char *p = name;
161 int i;
163 /* Check for "." and ".." */
164 if (*p == '.')
166 p++;
167 strcpy( buffer, ". " );
168 if (*p == '.')
170 buffer[1] = '.';
171 p++;
173 return (!*p || (*p == '/') || (*p == '\\'));
176 for (i = 0; i < 8; i++)
178 switch(*p)
180 case '\0':
181 case '\\':
182 case '/':
183 case '.':
184 buffer[i] = ' ';
185 break;
186 case '?':
187 p++;
188 /* fall through */
189 case '*':
190 buffer[i] = '?';
191 break;
192 default:
193 if (strchr( invalid_chars, *p )) return FALSE;
194 buffer[i] = toupper(*p);
195 p++;
196 break;
200 if (*p == '*')
202 /* Skip all chars after wildcard up to first dot */
203 while (*p && (*p != '/') && (*p != '\\') && (*p != '.')) p++;
205 else
207 /* Check if name too long */
208 if (*p && (*p != '/') && (*p != '\\') && (*p != '.')) return FALSE;
210 if (*p == '.') p++; /* Skip dot */
212 for (i = 8; i < 11; i++)
214 switch(*p)
216 case '\0':
217 case '\\':
218 case '/':
219 buffer[i] = ' ';
220 break;
221 case '.':
222 return FALSE; /* Second extension not allowed */
223 case '?':
224 p++;
225 /* fall through */
226 case '*':
227 buffer[i] = '?';
228 break;
229 default:
230 if (strchr( invalid_chars, *p )) return FALSE;
231 buffer[i] = toupper(*p);
232 p++;
233 break;
236 buffer[11] = '\0';
237 return TRUE;
241 /***********************************************************************
242 * DOSFS_ToDosDTAFormat
244 * Convert a file name from FCB to DTA format (name.ext, null-terminated)
245 * converting to upper-case in the process.
246 * File name can be terminated by '\0', '\\' or '/'.
247 * 'buffer' must be at least 13 characters long.
249 static void DOSFS_ToDosDTAFormat( LPCSTR name, LPSTR buffer )
251 char *p;
253 memcpy( buffer, name, 8 );
254 for (p = buffer + 8; (p > buffer) && (p[-1] == ' '); p--);
255 *p++ = '.';
256 memcpy( p, name + 8, 3 );
257 for (p += 3; p[-1] == ' '; p--);
258 if (p[-1] == '.') p--;
259 *p = '\0';
263 /***********************************************************************
264 * DOSFS_MatchShort
266 * Check a DOS file name against a mask (both in FCB format).
268 static int DOSFS_MatchShort( const char *mask, const char *name )
270 int i;
271 for (i = 11; i > 0; i--, mask++, name++)
272 if ((*mask != '?') && (*mask != *name)) return 0;
273 return 1;
277 /***********************************************************************
278 * DOSFS_MatchLong
280 * Check a long file name against a mask.
282 static int DOSFS_MatchLong( const char *mask, const char *name,
283 int case_sensitive )
285 if (!strcmp( mask, "*.*" )) return 1;
286 while (*name && *mask)
288 if (*mask == '*')
290 mask++;
291 while (*mask == '*') mask++; /* Skip consecutive '*' */
292 if (!*mask) return 1;
293 if (case_sensitive) while (*name && (*name != *mask)) name++;
294 else while (*name && (toupper(*name) != toupper(*mask))) name++;
295 if (!*name) return 0;
297 else if (*mask != '?')
299 if (case_sensitive)
301 if (*mask != *name) return 0;
303 else if (toupper(*mask) != toupper(*name)) return 0;
305 mask++;
306 name++;
308 return (!*name && !*mask);
312 /***********************************************************************
313 * DOSFS_OpenDir
315 static DOS_DIR *DOSFS_OpenDir( LPCSTR path )
317 DOS_DIR *dir = HeapAlloc( SystemHeap, 0, sizeof(*dir) );
318 if (!dir)
320 DOS_ERROR( ER_OutOfMemory, EC_OutOfResource, SA_Abort, EL_Memory );
321 return NULL;
324 /* Treat empty path as root directory. This simplifies path split into
325 directory and mask in several other places */
326 if (!*path) path = "/";
328 #ifdef VFAT_IOCTL_READDIR_BOTH
330 /* Check if the VFAT ioctl is supported on this directory */
332 if ((dir->fd = open( path, O_RDONLY )) != -1)
334 if (ioctl( dir->fd, VFAT_IOCTL_READDIR_BOTH, (long)dir->dirent ) == -1)
336 close( dir->fd );
337 dir->fd = -1;
339 else
341 /* Set the file pointer back at the start of the directory */
342 lseek( dir->fd, 0, SEEK_SET );
343 dir->dir = NULL;
344 return dir;
347 #endif /* VFAT_IOCTL_READDIR_BOTH */
349 /* Now use the standard opendir/readdir interface */
351 if (!(dir->dir = opendir( path )))
353 HeapFree( SystemHeap, 0, dir );
354 return NULL;
356 return dir;
360 /***********************************************************************
361 * DOSFS_CloseDir
363 static void DOSFS_CloseDir( DOS_DIR *dir )
365 #ifdef VFAT_IOCTL_READDIR_BOTH
366 if (dir->fd != -1) close( dir->fd );
367 #endif /* VFAT_IOCTL_READDIR_BOTH */
368 if (dir->dir) closedir( dir->dir );
369 HeapFree( SystemHeap, 0, dir );
373 /***********************************************************************
374 * DOSFS_ReadDir
376 static BOOL32 DOSFS_ReadDir( DOS_DIR *dir, LPCSTR *long_name,
377 LPCSTR *short_name )
379 struct dirent *dirent;
381 #ifdef VFAT_IOCTL_READDIR_BOTH
382 if (dir->fd != -1)
384 if (ioctl( dir->fd, VFAT_IOCTL_READDIR_BOTH, (long)dir->dirent ) != -1) {
385 if (!dir->dirent[0].d_reclen) return FALSE;
386 if (!DOSFS_ToDosFCBFormat( dir->dirent[0].d_name, dir->short_name ))
387 dir->short_name[0] = '\0';
388 *short_name = dir->short_name;
389 if (dir->dirent[1].d_name[0]) *long_name = dir->dirent[1].d_name;
390 else *long_name = dir->dirent[0].d_name;
391 return TRUE;
394 #endif /* VFAT_IOCTL_READDIR_BOTH */
396 if (!(dirent = readdir( dir->dir ))) return FALSE;
397 *long_name = dirent->d_name;
398 *short_name = NULL;
399 return TRUE;
403 /***********************************************************************
404 * DOSFS_Hash
406 * Transform a Unix file name into a hashed DOS name. If the name is a valid
407 * DOS name, it is converted to upper-case; otherwise it is replaced by a
408 * hashed version that fits in 8.3 format.
409 * File name can be terminated by '\0', '\\' or '/'.
410 * 'buffer' must be at least 13 characters long.
412 static void DOSFS_Hash( LPCSTR name, LPSTR buffer, BOOL32 dir_format,
413 BOOL32 ignore_case )
415 static const char invalid_chars[] = INVALID_DOS_CHARS "~.";
416 static const char hash_chars[32] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345";
418 const char *p, *ext;
419 char *dst;
420 unsigned short hash;
421 int i;
423 if (dir_format) strcpy( buffer, " " );
425 if (DOSFS_ValidDOSName( name, ignore_case ))
427 /* Check for '.' and '..' */
428 if (*name == '.')
430 buffer[0] = '.';
431 if (!dir_format) buffer[1] = buffer[2] = '\0';
432 if (name[1] == '.') buffer[1] = '.';
433 return;
436 /* Simply copy the name, converting to uppercase */
438 for (dst = buffer; !IS_END_OF_NAME(*name) && (*name != '.'); name++)
439 *dst++ = toupper(*name);
440 if (*name == '.')
442 if (dir_format) dst = buffer + 8;
443 else *dst++ = '.';
444 for (name++; !IS_END_OF_NAME(*name); name++)
445 *dst++ = toupper(*name);
447 if (!dir_format) *dst = '\0';
448 return;
451 /* Compute the hash code of the file name */
452 /* If you know something about hash functions, feel free to */
453 /* insert a better algorithm here... */
454 if (ignore_case)
456 for (p = name, hash = 0xbeef; !IS_END_OF_NAME(p[1]); p++)
457 hash = (hash<<3) ^ (hash>>5) ^ tolower(*p) ^ (tolower(p[1]) << 8);
458 hash = (hash<<3) ^ (hash>>5) ^ tolower(*p); /* Last character*/
460 else
462 for (p = name, hash = 0xbeef; !IS_END_OF_NAME(p[1]); p++)
463 hash = (hash << 3) ^ (hash >> 5) ^ *p ^ (p[1] << 8);
464 hash = (hash << 3) ^ (hash >> 5) ^ *p; /* Last character */
467 /* Find last dot for start of the extension */
468 for (p = name+1, ext = NULL; !IS_END_OF_NAME(*p); p++)
469 if (*p == '.') ext = p;
470 if (ext && IS_END_OF_NAME(ext[1]))
471 ext = NULL; /* Empty extension ignored */
473 /* Copy first 4 chars, replacing invalid chars with '_' */
474 for (i = 4, p = name, dst = buffer; i > 0; i--, p++)
476 if (IS_END_OF_NAME(*p) || (p == ext)) break;
477 *dst++ = strchr( invalid_chars, *p ) ? '_' : toupper(*p);
479 /* Pad to 5 chars with '~' */
480 while (i-- >= 0) *dst++ = '~';
482 /* Insert hash code converted to 3 ASCII chars */
483 *dst++ = hash_chars[(hash >> 10) & 0x1f];
484 *dst++ = hash_chars[(hash >> 5) & 0x1f];
485 *dst++ = hash_chars[hash & 0x1f];
487 /* Copy the first 3 chars of the extension (if any) */
488 if (ext)
490 if (!dir_format) *dst++ = '.';
491 for (i = 3, ext++; (i > 0) && !IS_END_OF_NAME(*ext); i--, ext++)
492 *dst++ = strchr( invalid_chars, *ext ) ? '_' : toupper(*ext);
494 if (!dir_format) *dst = '\0';
498 /***********************************************************************
499 * DOSFS_FindUnixName
501 * Find the Unix file name in a given directory that corresponds to
502 * a file name (either in Unix or DOS format).
503 * File name can be terminated by '\0', '\\' or '/'.
504 * Return TRUE if OK, FALSE if no file name matches.
506 * 'long_buf' must be at least 'long_len' characters long. If the long name
507 * turns out to be larger than that, the function returns FALSE.
508 * 'short_buf' must be at least 13 characters long.
510 BOOL32 DOSFS_FindUnixName( LPCSTR path, LPCSTR name, LPSTR long_buf,
511 INT32 long_len, LPSTR short_buf, BOOL32 ignore_case)
513 DOS_DIR *dir;
514 LPCSTR long_name, short_name;
515 char dos_name[12], tmp_buf[13];
516 BOOL32 ret;
518 const char *p = strchr( name, '/' );
519 int len = p ? (int)(p - name) : strlen(name);
520 if ((p = strchr( name, '\\' ))) len = MIN( (int)(p - name), len );
521 if (long_len < len + 1) return FALSE;
523 TRACE(dosfs, "%s,%s\n", path, name );
525 if (!DOSFS_ToDosFCBFormat( name, dos_name )) dos_name[0] = '\0';
527 if (!(dir = DOSFS_OpenDir( path )))
529 WARN(dosfs, "(%s,%s): can't open dir: %s\n",
530 path, name, strerror(errno) );
531 return FALSE;
534 while ((ret = DOSFS_ReadDir( dir, &long_name, &short_name )))
536 /* Check against Unix name */
537 if (len == strlen(long_name))
539 if (!ignore_case)
541 if (!lstrncmp32A( long_name, name, len )) break;
543 else
545 if (!lstrncmpi32A( long_name, name, len )) break;
548 if (dos_name[0])
550 /* Check against hashed DOS name */
551 if (!short_name)
553 DOSFS_Hash( long_name, tmp_buf, TRUE, ignore_case );
554 short_name = tmp_buf;
556 if (!strcmp( dos_name, short_name )) break;
559 if (ret)
561 if (long_buf) strcpy( long_buf, long_name );
562 if (short_buf)
564 if (short_name)
565 DOSFS_ToDosDTAFormat( short_name, short_buf );
566 else
567 DOSFS_Hash( long_name, short_buf, FALSE, ignore_case );
569 TRACE(dosfs, "(%s,%s) -> %s (%s)\n",
570 path, name, long_name, short_buf ? short_buf : "***");
572 else
573 WARN(dosfs, "'%s' not found in '%s'\n", name, path);
574 DOSFS_CloseDir( dir );
575 return ret;
579 /***********************************************************************
580 * DOSFS_GetDevice
582 * Check if a DOS file name represents a DOS device and return the device.
584 const DOS_DEVICE *DOSFS_GetDevice( const char *name )
586 int i;
587 const char *p;
589 if (!name) return NULL; /* if FILE_DupUnixHandle was used */
590 if (name[0] && (name[1] == ':')) name += 2;
591 if ((p = strrchr( name, '/' ))) name = p + 1;
592 if ((p = strrchr( name, '\\' ))) name = p + 1;
593 for (i = 0; i < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0]); i++)
595 const char *dev = DOSFS_Devices[i].name;
596 if (!lstrncmpi32A( dev, name, strlen(dev) ))
598 p = name + strlen( dev );
599 if (!*p || (*p == '.')) return &DOSFS_Devices[i];
602 return NULL;
605 /***********************************************************************
606 * DOSFS_OpenDevice
608 * Open a DOS device. This might not map 1:1 into the UNIX device concept.
610 HFILE32 DOSFS_OpenDevice( const char *name, DWORD access )
612 int i;
613 const char *p;
615 if (!name) return (HFILE32)NULL; /* if FILE_DupUnixHandle was used */
616 if (name[0] && (name[1] == ':')) name += 2;
617 if ((p = strrchr( name, '/' ))) name = p + 1;
618 if ((p = strrchr( name, '\\' ))) name = p + 1;
619 for (i = 0; i < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0]); i++)
621 const char *dev = DOSFS_Devices[i].name;
622 if (!lstrncmpi32A( dev, name, strlen(dev) ))
624 p = name + strlen( dev );
625 if (!*p || (*p == '.')) {
626 /* got it */
627 if (!strcmp(DOSFS_Devices[i].name,"NUL"))
628 return FILE_CreateFile( "/dev/null", access,
629 FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,
630 OPEN_EXISTING, 0, -1 );
631 if (!strcmp(DOSFS_Devices[i].name,"CON")) {
632 HFILE32 to_dup;
633 HFILE32 handle;
634 switch (access & (GENERIC_READ|GENERIC_WRITE)) {
635 case GENERIC_READ:
636 to_dup = GetStdHandle( STD_INPUT_HANDLE );
637 break;
638 case GENERIC_WRITE:
639 to_dup = GetStdHandle( STD_OUTPUT_HANDLE );
640 break;
641 default:
642 FIXME(dosfs,"can't open CON read/write\n");
643 return HFILE_ERROR32;
644 break;
646 if (!DuplicateHandle( GetCurrentProcess(), to_dup, GetCurrentProcess(),
647 &handle, 0, FALSE, DUPLICATE_SAME_ACCESS ))
648 handle = HFILE_ERROR32;
649 return handle;
651 if (!strcmp(DOSFS_Devices[i].name,"SCSIMGR$") ||
652 !strcmp(DOSFS_Devices[i].name,"HPSCAN"))
654 /* FIXME: should keep the name somewhere */
655 return FILE_CreateFile( "/dev/null", access,
656 FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,
657 OPEN_EXISTING, 0, -1 );
659 FIXME(dosfs,"device open %s not supported (yet)\n",DOSFS_Devices[i].name);
660 return HFILE_ERROR32;
664 return HFILE_ERROR32;
668 /***********************************************************************
669 * DOSFS_GetPathDrive
671 * Get the drive specified by a given path name (DOS or Unix format).
673 static int DOSFS_GetPathDrive( const char **name )
675 int drive;
676 const char *p = *name;
678 if (*p && (p[1] == ':'))
680 drive = toupper(*p) - 'A';
681 *name += 2;
683 else if (*p == '/') /* Absolute Unix path? */
685 if ((drive = DRIVE_FindDriveRoot( name )) == -1)
687 MSG("Warning: %s not accessible from a DOS drive\n", *name );
688 /* Assume it really was a DOS name */
689 drive = DRIVE_GetCurrentDrive();
692 else drive = DRIVE_GetCurrentDrive();
694 if (!DRIVE_IsValid(drive))
696 DOS_ERROR( ER_InvalidDrive, EC_MediaError, SA_Abort, EL_Disk );
697 return -1;
699 return drive;
703 /***********************************************************************
704 * DOSFS_GetFullName
706 * Convert a file name (DOS or mixed DOS/Unix format) to a valid
707 * Unix name / short DOS name pair.
708 * Return FALSE if one of the path components does not exist. The last path
709 * component is only checked if 'check_last' is non-zero.
710 * The buffers pointed to by 'long_buf' and 'short_buf' must be
711 * at least MAX_PATHNAME_LEN long.
713 BOOL32 DOSFS_GetFullName( LPCSTR name, BOOL32 check_last, DOS_FULL_NAME *full )
715 BOOL32 found;
716 UINT32 flags;
717 char *p_l, *p_s, *root;
719 TRACE(dosfs, "%s (last=%d)\n",
720 name, check_last );
722 if ((full->drive = DOSFS_GetPathDrive( &name )) == -1) return FALSE;
723 flags = DRIVE_GetFlags( full->drive );
725 lstrcpyn32A( full->long_name, DRIVE_GetRoot( full->drive ),
726 sizeof(full->long_name) );
727 if (full->long_name[1]) root = full->long_name + strlen(full->long_name);
728 else root = full->long_name; /* root directory */
730 strcpy( full->short_name, "A:\\" );
731 full->short_name[0] += full->drive;
733 if ((*name == '\\') || (*name == '/')) /* Absolute path */
735 while ((*name == '\\') || (*name == '/')) name++;
737 else /* Relative path */
739 lstrcpyn32A( root + 1, DRIVE_GetUnixCwd( full->drive ),
740 sizeof(full->long_name) - (root - full->long_name) - 1 );
741 if (root[1]) *root = '/';
742 lstrcpyn32A( full->short_name + 3, DRIVE_GetDosCwd( full->drive ),
743 sizeof(full->short_name) - 3 );
746 p_l = full->long_name[1] ? full->long_name + strlen(full->long_name)
747 : full->long_name;
748 p_s = full->short_name[3] ? full->short_name + strlen(full->short_name)
749 : full->short_name + 2;
750 found = TRUE;
752 while (*name && found)
754 /* Check for '.' and '..' */
756 if (*name == '.')
758 if (IS_END_OF_NAME(name[1]))
760 name++;
761 while ((*name == '\\') || (*name == '/')) name++;
762 continue;
764 else if ((name[1] == '.') && IS_END_OF_NAME(name[2]))
766 name += 2;
767 while ((*name == '\\') || (*name == '/')) name++;
768 while ((p_l > root) && (*p_l != '/')) p_l--;
769 while ((p_s > full->short_name + 2) && (*p_s != '\\')) p_s--;
770 *p_l = *p_s = '\0'; /* Remove trailing separator */
771 continue;
775 /* Make sure buffers are large enough */
777 if ((p_s >= full->short_name + sizeof(full->short_name) - 14) ||
778 (p_l >= full->long_name + sizeof(full->long_name) - 1))
780 DOS_ERROR( ER_PathNotFound, EC_NotFound, SA_Abort, EL_Disk );
781 return FALSE;
784 /* Get the long and short name matching the file name */
786 if ((found = DOSFS_FindUnixName( full->long_name, name, p_l + 1,
787 sizeof(full->long_name) - (p_l - full->long_name) - 1,
788 p_s + 1, !(flags & DRIVE_CASE_SENSITIVE) )))
790 *p_l++ = '/';
791 p_l += strlen(p_l);
792 *p_s++ = '\\';
793 p_s += strlen(p_s);
794 while (!IS_END_OF_NAME(*name)) name++;
796 else if (!check_last)
798 *p_l++ = '/';
799 *p_s++ = '\\';
800 while (!IS_END_OF_NAME(*name) &&
801 (p_s < full->short_name + sizeof(full->short_name) - 1) &&
802 (p_l < full->long_name + sizeof(full->long_name) - 1))
804 *p_s++ = tolower(*name);
805 /* If the drive is case-sensitive we want to create new */
806 /* files in lower-case otherwise we can't reopen them */
807 /* under the same short name. */
808 if (flags & DRIVE_CASE_SENSITIVE) *p_l++ = tolower(*name);
809 else *p_l++ = *name;
810 name++;
812 *p_l = *p_s = '\0';
814 while ((*name == '\\') || (*name == '/')) name++;
817 if (!found)
819 if (check_last)
821 DOS_ERROR( ER_FileNotFound, EC_NotFound, SA_Abort, EL_Disk );
822 return FALSE;
824 if (*name) /* Not last */
826 DOS_ERROR( ER_PathNotFound, EC_NotFound, SA_Abort, EL_Disk );
827 return FALSE;
830 if (!full->long_name[0]) strcpy( full->long_name, "/" );
831 if (!full->short_name[2]) strcpy( full->short_name + 2, "\\" );
832 TRACE(dosfs, "returning %s = %s\n",
833 full->long_name, full->short_name );
834 return TRUE;
838 /***********************************************************************
839 * GetShortPathName32A (KERNEL32.271)
841 * NOTES
842 * observed:
843 * longpath=NULL: LastError=ERROR_INVALID_PARAMETER, ret=0
844 * *longpath="" or invalid: LastError=ERROR_BAD_PATHNAME, ret=0
846 DWORD WINAPI GetShortPathName32A( LPCSTR longpath, LPSTR shortpath,
847 DWORD shortlen )
849 DOS_FULL_NAME full_name;
851 if (!longpath)
853 SetLastError(ERROR_INVALID_PARAMETER);
854 return 0;
857 if (!longpath[0])
859 SetLastError(ERROR_BAD_PATHNAME);
860 return 0;
863 /* FIXME: is it correct to always return a fully qualified short path? */
864 if (!DOSFS_GetFullName( longpath, TRUE, &full_name ))
866 SetLastError(ERROR_BAD_PATHNAME);
867 return 0;
869 lstrcpyn32A( shortpath, full_name.short_name, shortlen );
870 return strlen( full_name.short_name );
874 /***********************************************************************
875 * GetShortPathName32W (KERNEL32.272)
877 DWORD WINAPI GetShortPathName32W( LPCWSTR longpath, LPWSTR shortpath,
878 DWORD shortlen )
880 DOS_FULL_NAME full_name;
881 LPSTR longpathA ;
882 DWORD ret = 0;
884 if (!longpath)
885 { SetLastError(ERROR_INVALID_PARAMETER);
886 return 0;
889 if (!longpath[0])
890 { SetLastError(ERROR_BAD_PATHNAME);
891 return 0;
895 longpathA = HEAP_strdupWtoA( GetProcessHeap(), 0, longpath );
897 /* FIXME: is it correct to always return a fully qualified short path? */
898 if (DOSFS_GetFullName( longpathA, TRUE, &full_name ))
900 ret = strlen( full_name.short_name );
901 lstrcpynAtoW( shortpath, full_name.short_name, shortlen );
904 SetLastError(ERROR_BAD_PATHNAME);
905 HeapFree( GetProcessHeap(), 0, longpathA );
906 return 0;
910 /***********************************************************************
911 * GetLongPathName32A (KERNEL32.xxx)
913 DWORD WINAPI GetLongPathName32A( LPCSTR shortpath, LPSTR longpath,
914 DWORD longlen )
916 DOS_FULL_NAME full_name;
917 char *p;
918 char *longfilename;
919 DWORD shortpathlen;
921 if (!DOSFS_GetFullName( shortpath, TRUE, &full_name )) return 0;
922 lstrcpyn32A( longpath, full_name.short_name, longlen );
923 /* Do some hackery to get the long filename.
924 * FIXME: Would be better if it returned the
925 * long version of the directories too
927 longfilename = strrchr(full_name.long_name, '/')+1;
928 if (longpath != NULL) {
929 if ((p = strrchr( longpath, '\\' )) != NULL) {
930 p++;
931 longlen -= (p-longpath);
932 lstrcpyn32A( p, longfilename , longlen);
935 shortpathlen =
936 ((strrchr( full_name.short_name, '\\' ) - full_name.short_name) + 1);
937 return shortpathlen + strlen( longfilename );
941 /***********************************************************************
942 * GetLongPathName32W (KERNEL32.269)
944 DWORD WINAPI GetLongPathName32W( LPCWSTR shortpath, LPWSTR longpath,
945 DWORD longlen )
947 DOS_FULL_NAME full_name;
948 DWORD ret = 0;
949 LPSTR shortpathA = HEAP_strdupWtoA( GetProcessHeap(), 0, shortpath );
951 /* FIXME: is it correct to always return a fully qualified short path? */
952 if (DOSFS_GetFullName( shortpathA, TRUE, &full_name ))
954 ret = strlen( full_name.short_name );
955 lstrcpynAtoW( longpath, full_name.long_name, longlen );
957 HeapFree( GetProcessHeap(), 0, shortpathA );
958 return ret;
962 /***********************************************************************
963 * DOSFS_DoGetFullPathName
965 * Implementation of GetFullPathName32A/W.
967 static DWORD DOSFS_DoGetFullPathName( LPCSTR name, DWORD len, LPSTR result,
968 BOOL32 unicode )
970 char buffer[MAX_PATHNAME_LEN];
971 int drive;
972 char *p;
973 DWORD ret;
975 /* last possible position for a char != 0 */
976 char *endchar = buffer + sizeof(buffer) - 2;
977 *endchar = '\0';
979 TRACE(dosfs, "converting '%s'\n", name );
981 if (!name || !result || ((drive = DOSFS_GetPathDrive( &name )) == -1) )
982 { SetLastError( ERROR_INVALID_PARAMETER );
983 return 0;
986 p = buffer;
987 *p++ = 'A' + drive;
988 *p++ = ':';
989 if (IS_END_OF_NAME(*name) && (*name)) /* Absolute path */
991 while (((*name == '\\') || (*name == '/')) && (!*endchar) )
992 *p++ = *name++;
994 else /* Relative path or empty path */
996 *p++ = '\\';
997 lstrcpyn32A( p, DRIVE_GetDosCwd(drive), sizeof(buffer) - 4 );
998 if ( *p )
1000 p += strlen(p);
1001 *p++ = '\\';
1004 *p = '\0';
1006 while (*name)
1008 if (*name == '.')
1010 if (IS_END_OF_NAME(name[1]))
1012 name++;
1013 while ((*name == '\\') || (*name == '/')) name++;
1014 continue;
1016 else if ((name[1] == '.') && IS_END_OF_NAME(name[2]))
1018 name += 2;
1019 while ((*name == '\\') || (*name == '/')) name++;
1021 if (p < buffer + 3) /* no previous dir component */
1022 continue;
1023 p--; /* skip previously added '\\' */
1024 while ((*p == '\\') || (*p == '/')) p--;
1025 /* skip previous dir component */
1026 while ((*p != '\\') && (*p != '/')) p--;
1027 p++;
1028 continue;
1031 if ( *endchar )
1032 { DOS_ERROR( ER_PathNotFound, EC_NotFound, SA_Abort, EL_Disk );
1033 return 0;
1035 while (!IS_END_OF_NAME(*name) && (!*endchar) )
1036 *p++ = *name++;
1037 while (((*name == '\\') || (*name == '/')) && (!*endchar) )
1038 *p++ = *name++;
1040 *p = '\0';
1042 if (!(DRIVE_GetFlags(drive) & DRIVE_CASE_PRESERVING))
1043 CharUpper32A( buffer );
1045 if (unicode)
1046 lstrcpynAtoW( (LPWSTR)result, buffer, len );
1047 else
1048 lstrcpyn32A( result, buffer, len );
1050 TRACE(dosfs, "returning '%s'\n", buffer );
1052 /* If the lpBuffer buffer is too small, the return value is the
1053 size of the buffer, in characters, required to hold the path. */
1055 ret = strlen(buffer);
1057 if (ret >= len )
1058 SetLastError( ERROR_INSUFFICIENT_BUFFER );
1060 return ret;
1064 /***********************************************************************
1065 * GetFullPathName32A (KERNEL32.272)
1066 * NOTES
1067 * if the path closed with '\', *lastpart is 0
1069 DWORD WINAPI GetFullPathName32A( LPCSTR name, DWORD len, LPSTR buffer,
1070 LPSTR *lastpart )
1072 DWORD ret = DOSFS_DoGetFullPathName( name, len, buffer, FALSE );
1073 if (ret && lastpart)
1075 LPSTR p = buffer + strlen(buffer);
1077 if (*p != '\\')
1079 while ((p > buffer + 2) && (*p != '\\')) p--;
1080 *lastpart = p + 1;
1082 else *lastpart = NULL;
1084 return ret;
1088 /***********************************************************************
1089 * GetFullPathName32W (KERNEL32.273)
1091 DWORD WINAPI GetFullPathName32W( LPCWSTR name, DWORD len, LPWSTR buffer,
1092 LPWSTR *lastpart )
1094 LPSTR nameA = HEAP_strdupWtoA( GetProcessHeap(), 0, name );
1095 DWORD ret = DOSFS_DoGetFullPathName( nameA, len, (LPSTR)buffer, TRUE );
1096 HeapFree( GetProcessHeap(), 0, nameA );
1097 if (ret && lastpart)
1099 LPWSTR p = buffer + lstrlen32W(buffer);
1100 if (*p != (WCHAR)'\\')
1102 while ((p > buffer + 2) && (*p != (WCHAR)'\\')) p--;
1103 *lastpart = p + 1;
1105 else *lastpart = NULL;
1107 return ret;
1110 /***********************************************************************
1111 * DOSFS_FindNextEx
1113 static int DOSFS_FindNextEx( FIND_FIRST_INFO *info, WIN32_FIND_DATA32A *entry )
1115 BYTE attr = info->attr | FA_UNUSED | FA_ARCHIVE | FA_RDONLY;
1116 UINT32 flags = DRIVE_GetFlags( info->drive );
1117 char *p, buffer[MAX_PATHNAME_LEN];
1118 const char *drive_path;
1119 int drive_root;
1120 LPCSTR long_name, short_name;
1121 BY_HANDLE_FILE_INFORMATION fileinfo;
1122 char dos_name[13];
1124 if ((info->attr & ~(FA_UNUSED | FA_ARCHIVE | FA_RDONLY)) == FA_LABEL)
1126 if (info->cur_pos) return 0;
1127 entry->dwFileAttributes = FILE_ATTRIBUTE_LABEL;
1128 DOSFS_UnixTimeToFileTime( (time_t)0, &entry->ftCreationTime, 0 );
1129 DOSFS_UnixTimeToFileTime( (time_t)0, &entry->ftLastAccessTime, 0 );
1130 DOSFS_UnixTimeToFileTime( (time_t)0, &entry->ftLastWriteTime, 0 );
1131 entry->nFileSizeHigh = 0;
1132 entry->nFileSizeLow = 0;
1133 entry->dwReserved0 = 0;
1134 entry->dwReserved1 = 0;
1135 DOSFS_ToDosDTAFormat( DRIVE_GetLabel( info->drive ), entry->cFileName );
1136 strcpy( entry->cAlternateFileName, entry->cFileName );
1137 info->cur_pos++;
1138 return 1;
1141 drive_path = info->path + strlen(DRIVE_GetRoot( info->drive ));
1142 while ((*drive_path == '/') || (*drive_path == '\\')) drive_path++;
1143 drive_root = !*drive_path;
1145 lstrcpyn32A( buffer, info->path, sizeof(buffer) - 1 );
1146 strcat( buffer, "/" );
1147 p = buffer + strlen(buffer);
1149 while (DOSFS_ReadDir( info->dir, &long_name, &short_name ))
1151 info->cur_pos++;
1153 /* Don't return '.' and '..' in the root of the drive */
1154 if (drive_root && (long_name[0] == '.') &&
1155 (!long_name[1] || ((long_name[1] == '.') && !long_name[2])))
1156 continue;
1158 /* Check the long mask */
1160 if (info->long_mask)
1162 if (!DOSFS_MatchLong( info->long_mask, long_name,
1163 flags & DRIVE_CASE_SENSITIVE )) continue;
1166 /* Check the short mask */
1168 if (info->short_mask)
1170 if (!short_name)
1172 DOSFS_Hash( long_name, dos_name, TRUE,
1173 !(flags & DRIVE_CASE_SENSITIVE) );
1174 short_name = dos_name;
1176 if (!DOSFS_MatchShort( info->short_mask, short_name )) continue;
1179 /* Check the file attributes */
1181 lstrcpyn32A( p, long_name, sizeof(buffer) - (int)(p - buffer) );
1182 if (!FILE_Stat( buffer, &fileinfo ))
1184 WARN(dosfs, "can't stat %s\n", buffer);
1185 continue;
1187 if (fileinfo.dwFileAttributes & ~attr) continue;
1189 /* We now have a matching entry; fill the result and return */
1191 entry->dwFileAttributes = fileinfo.dwFileAttributes;
1192 entry->ftCreationTime = fileinfo.ftCreationTime;
1193 entry->ftLastAccessTime = fileinfo.ftLastAccessTime;
1194 entry->ftLastWriteTime = fileinfo.ftLastWriteTime;
1195 entry->nFileSizeHigh = fileinfo.nFileSizeHigh;
1196 entry->nFileSizeLow = fileinfo.nFileSizeLow;
1198 if (short_name)
1199 DOSFS_ToDosDTAFormat( short_name, entry->cAlternateFileName );
1200 else
1201 DOSFS_Hash( long_name, entry->cAlternateFileName, FALSE,
1202 !(flags & DRIVE_CASE_SENSITIVE) );
1204 lstrcpyn32A( entry->cFileName, long_name, sizeof(entry->cFileName) );
1205 if (!(flags & DRIVE_CASE_PRESERVING)) CharLower32A( entry->cFileName );
1206 TRACE(dosfs, "returning %s (%s) %02lx %ld\n",
1207 entry->cFileName, entry->cAlternateFileName,
1208 entry->dwFileAttributes, entry->nFileSizeLow );
1209 return 1;
1211 return 0; /* End of directory */
1214 /***********************************************************************
1215 * DOSFS_FindNext
1217 * Find the next matching file. Return the number of entries read to find
1218 * the matching one, or 0 if no more entries.
1219 * 'short_mask' is the 8.3 mask (in FCB format), 'long_mask' is the long
1220 * file name mask. Either or both can be NULL.
1222 * NOTE: This is supposed to be only called by the int21 emulation
1223 * routines. Thus, we should own the Win16Mutex anyway.
1224 * Nevertheless, we explicitly enter it to ensure the static
1225 * directory cache is protected.
1227 int DOSFS_FindNext( const char *path, const char *short_mask,
1228 const char *long_mask, int drive, BYTE attr,
1229 int skip, WIN32_FIND_DATA32A *entry )
1231 static FIND_FIRST_INFO info = { NULL };
1232 LPCSTR short_name, long_name;
1233 int count;
1235 SYSLEVEL_EnterWin16Lock();
1237 /* Check the cached directory */
1238 if (!(info.dir && info.path == path && info.short_mask == short_mask
1239 && info.long_mask == long_mask && info.drive == drive
1240 && info.attr == attr && info.cur_pos <= skip))
1242 /* Not in the cache, open it anew */
1243 if (info.dir) DOSFS_CloseDir( info.dir );
1245 info.path = (LPSTR)path;
1246 info.long_mask = (LPSTR)long_mask;
1247 info.short_mask = (LPSTR)short_mask;
1248 info.attr = attr;
1249 info.drive = drive;
1250 info.cur_pos = 0;
1251 info.dir = DOSFS_OpenDir( info.path );
1254 /* Skip to desired position */
1255 while (info.cur_pos < skip)
1256 if (info.dir && DOSFS_ReadDir( info.dir, &long_name, &short_name ))
1257 info.cur_pos++;
1258 else
1259 break;
1261 if (info.dir && info.cur_pos == skip && DOSFS_FindNextEx( &info, entry ))
1262 count = info.cur_pos - skip;
1263 else
1264 count = 0;
1266 if (!count)
1268 if (info.dir) DOSFS_CloseDir( info.dir );
1269 memset( &info, '\0', sizeof(info) );
1272 SYSLEVEL_LeaveWin16Lock();
1274 return count;
1279 /*************************************************************************
1280 * FindFirstFile16 (KERNEL.413)
1282 HANDLE16 WINAPI FindFirstFile16( LPCSTR path, WIN32_FIND_DATA32A *data )
1284 DOS_FULL_NAME full_name;
1285 HGLOBAL16 handle;
1286 FIND_FIRST_INFO *info;
1288 data->dwReserved0 = data->dwReserved1 = 0x0;
1289 if (!path) return 0;
1290 if (!DOSFS_GetFullName( path, FALSE, &full_name ))
1291 return INVALID_HANDLE_VALUE16;
1292 if (!(handle = GlobalAlloc16( GMEM_MOVEABLE, sizeof(FIND_FIRST_INFO) )))
1293 return INVALID_HANDLE_VALUE16;
1294 info = (FIND_FIRST_INFO *)GlobalLock16( handle );
1295 info->path = HEAP_strdupA( SystemHeap, 0, full_name.long_name );
1296 info->long_mask = strrchr( info->path, '/' );
1297 *(info->long_mask++) = '\0';
1298 info->short_mask = NULL;
1299 info->attr = 0xff;
1300 if (path[0] && (path[1] == ':')) info->drive = toupper(*path) - 'A';
1301 else info->drive = DRIVE_GetCurrentDrive();
1302 info->cur_pos = 0;
1304 info->dir = DOSFS_OpenDir( info->path );
1306 GlobalUnlock16( handle );
1307 if (!FindNextFile16( handle, data ))
1309 FindClose16( handle );
1310 DOS_ERROR( ER_NoMoreFiles, EC_MediaError, SA_Abort, EL_Disk );
1311 return INVALID_HANDLE_VALUE16;
1313 return handle;
1317 /*************************************************************************
1318 * FindFirstFile32A (KERNEL32.123)
1320 HANDLE32 WINAPI FindFirstFile32A( LPCSTR path, WIN32_FIND_DATA32A *data )
1322 HANDLE32 handle = FindFirstFile16( path, data );
1323 if (handle == INVALID_HANDLE_VALUE16) return INVALID_HANDLE_VALUE32;
1324 return handle;
1328 /*************************************************************************
1329 * FindFirstFile32W (KERNEL32.124)
1331 HANDLE32 WINAPI FindFirstFile32W( LPCWSTR path, WIN32_FIND_DATA32W *data )
1333 WIN32_FIND_DATA32A dataA;
1334 LPSTR pathA = HEAP_strdupWtoA( GetProcessHeap(), 0, path );
1335 HANDLE32 handle = FindFirstFile32A( pathA, &dataA );
1336 HeapFree( GetProcessHeap(), 0, pathA );
1337 if (handle != INVALID_HANDLE_VALUE32)
1339 data->dwFileAttributes = dataA.dwFileAttributes;
1340 data->ftCreationTime = dataA.ftCreationTime;
1341 data->ftLastAccessTime = dataA.ftLastAccessTime;
1342 data->ftLastWriteTime = dataA.ftLastWriteTime;
1343 data->nFileSizeHigh = dataA.nFileSizeHigh;
1344 data->nFileSizeLow = dataA.nFileSizeLow;
1345 lstrcpyAtoW( data->cFileName, dataA.cFileName );
1346 lstrcpyAtoW( data->cAlternateFileName, dataA.cAlternateFileName );
1348 return handle;
1352 /*************************************************************************
1353 * FindNextFile16 (KERNEL.414)
1355 BOOL16 WINAPI FindNextFile16( HANDLE16 handle, WIN32_FIND_DATA32A *data )
1357 FIND_FIRST_INFO *info;
1359 if (!(info = (FIND_FIRST_INFO *)GlobalLock16( handle )))
1361 DOS_ERROR( ER_InvalidHandle, EC_ProgramError, SA_Abort, EL_Disk );
1362 return FALSE;
1364 GlobalUnlock16( handle );
1365 if (!info->path || !info->dir)
1367 DOS_ERROR( ER_NoMoreFiles, EC_MediaError, SA_Abort, EL_Disk );
1368 return FALSE;
1370 if (!DOSFS_FindNextEx( info, data ))
1372 DOSFS_CloseDir( info->dir ); info->dir = NULL;
1373 HeapFree( SystemHeap, 0, info->path );
1374 info->path = info->long_mask = NULL;
1375 DOS_ERROR( ER_NoMoreFiles, EC_MediaError, SA_Abort, EL_Disk );
1376 return FALSE;
1378 return TRUE;
1382 /*************************************************************************
1383 * FindNextFile32A (KERNEL32.126)
1385 BOOL32 WINAPI FindNextFile32A( HANDLE32 handle, WIN32_FIND_DATA32A *data )
1387 return FindNextFile16( handle, data );
1391 /*************************************************************************
1392 * FindNextFile32W (KERNEL32.127)
1394 BOOL32 WINAPI FindNextFile32W( HANDLE32 handle, WIN32_FIND_DATA32W *data )
1396 WIN32_FIND_DATA32A dataA;
1397 if (!FindNextFile32A( handle, &dataA )) return FALSE;
1398 data->dwFileAttributes = dataA.dwFileAttributes;
1399 data->ftCreationTime = dataA.ftCreationTime;
1400 data->ftLastAccessTime = dataA.ftLastAccessTime;
1401 data->ftLastWriteTime = dataA.ftLastWriteTime;
1402 data->nFileSizeHigh = dataA.nFileSizeHigh;
1403 data->nFileSizeLow = dataA.nFileSizeLow;
1404 lstrcpyAtoW( data->cFileName, dataA.cFileName );
1405 lstrcpyAtoW( data->cAlternateFileName, dataA.cAlternateFileName );
1406 return TRUE;
1410 /*************************************************************************
1411 * FindClose16 (KERNEL.415)
1413 BOOL16 WINAPI FindClose16( HANDLE16 handle )
1415 FIND_FIRST_INFO *info;
1417 if ((handle == INVALID_HANDLE_VALUE16) ||
1418 !(info = (FIND_FIRST_INFO *)GlobalLock16( handle )))
1420 DOS_ERROR( ER_InvalidHandle, EC_ProgramError, SA_Abort, EL_Disk );
1421 return FALSE;
1423 if (info->dir) DOSFS_CloseDir( info->dir );
1424 if (info->path) HeapFree( SystemHeap, 0, info->path );
1425 GlobalUnlock16( handle );
1426 GlobalFree16( handle );
1427 return TRUE;
1431 /*************************************************************************
1432 * FindClose32 (KERNEL32.119)
1434 BOOL32 WINAPI FindClose32( HANDLE32 handle )
1436 return FindClose16( (HANDLE16)handle );
1440 /***********************************************************************
1441 * DOSFS_UnixTimeToFileTime
1443 * Convert a Unix time to FILETIME format.
1444 * The FILETIME structure is a 64-bit value representing the number of
1445 * 100-nanosecond intervals since January 1, 1601, 0:00.
1446 * 'remainder' is the nonnegative number of 100-ns intervals
1447 * corresponding to the time fraction smaller than 1 second that
1448 * couldn't be stored in the time_t value.
1450 void DOSFS_UnixTimeToFileTime( time_t unix_time, FILETIME *filetime,
1451 DWORD remainder )
1453 /* NOTES:
1455 CONSTANTS:
1456 The time difference between 1 January 1601, 00:00:00 and
1457 1 January 1970, 00:00:00 is 369 years, plus the leap years
1458 from 1604 to 1968, excluding 1700, 1800, 1900.
1459 This makes (1968 - 1600) / 4 - 3 = 89 leap days, and a total
1460 of 134774 days.
1462 Any day in that period had 24 * 60 * 60 = 86400 seconds.
1464 The time difference is 134774 * 86400 * 10000000, which can be written
1465 116444736000000000
1466 27111902 * 2^32 + 3577643008
1467 413 * 2^48 + 45534 * 2^32 + 54590 * 2^16 + 32768
1469 If you find that these constants are buggy, please change them in all
1470 instances in both conversion functions.
1472 VERSIONS:
1473 There are two versions, one of them uses long long variables and
1474 is presumably faster but not ISO C. The other one uses standard C
1475 data types and operations but relies on the assumption that negative
1476 numbers are stored as 2's complement (-1 is 0xffff....). If this
1477 assumption is violated, dates before 1970 will not convert correctly.
1478 This should however work on any reasonable architecture where WINE
1479 will run.
1481 DETAILS:
1483 Take care not to remove the casts. I have tested these functions
1484 (in both versions) for a lot of numbers. I would be interested in
1485 results on other compilers than GCC.
1487 The operations have been designed to account for the possibility
1488 of 64-bit time_t in future UNICES. Even the versions without
1489 internal long long numbers will work if time_t only is 64 bit.
1490 A 32-bit shift, which was necessary for that operation, turned out
1491 not to work correctly in GCC, besides giving the warning. So I
1492 used a double 16-bit shift instead. Numbers are in the ISO version
1493 represented by three limbs, the most significant with 32 bit, the
1494 other two with 16 bit each.
1496 As the modulo-operator % is not well-defined for negative numbers,
1497 negative divisors have been avoided in DOSFS_FileTimeToUnixTime.
1499 There might be quicker ways to do this in C. Certainly so in
1500 assembler.
1502 Claus Fischer, fischer@iue.tuwien.ac.at
1505 #if (SIZEOF_LONG_LONG >= 8)
1506 # define USE_LONG_LONG 1
1507 #else
1508 # define USE_LONG_LONG 0
1509 #endif
1511 #if USE_LONG_LONG /* gcc supports long long type */
1513 long long int t = unix_time;
1514 t *= 10000000;
1515 t += 116444736000000000LL;
1516 t += remainder;
1517 filetime->dwLowDateTime = (UINT32)t;
1518 filetime->dwHighDateTime = (UINT32)(t >> 32);
1520 #else /* ISO version */
1522 UINT32 a0; /* 16 bit, low bits */
1523 UINT32 a1; /* 16 bit, medium bits */
1524 UINT32 a2; /* 32 bit, high bits */
1526 /* Copy the unix time to a2/a1/a0 */
1527 a0 = unix_time & 0xffff;
1528 a1 = (unix_time >> 16) & 0xffff;
1529 /* This is obsolete if unix_time is only 32 bits, but it does not hurt.
1530 Do not replace this by >> 32, it gives a compiler warning and it does
1531 not work. */
1532 a2 = (unix_time >= 0 ? (unix_time >> 16) >> 16 :
1533 ~((~unix_time >> 16) >> 16));
1535 /* Multiply a by 10000000 (a = a2/a1/a0)
1536 Split the factor into 10000 * 1000 which are both less than 0xffff. */
1537 a0 *= 10000;
1538 a1 = a1 * 10000 + (a0 >> 16);
1539 a2 = a2 * 10000 + (a1 >> 16);
1540 a0 &= 0xffff;
1541 a1 &= 0xffff;
1543 a0 *= 1000;
1544 a1 = a1 * 1000 + (a0 >> 16);
1545 a2 = a2 * 1000 + (a1 >> 16);
1546 a0 &= 0xffff;
1547 a1 &= 0xffff;
1549 /* Add the time difference and the remainder */
1550 a0 += 32768 + (remainder & 0xffff);
1551 a1 += 54590 + (remainder >> 16 ) + (a0 >> 16);
1552 a2 += 27111902 + (a1 >> 16);
1553 a0 &= 0xffff;
1554 a1 &= 0xffff;
1556 /* Set filetime */
1557 filetime->dwLowDateTime = (a1 << 16) + a0;
1558 filetime->dwHighDateTime = a2;
1559 #endif
1563 /***********************************************************************
1564 * DOSFS_FileTimeToUnixTime
1566 * Convert a FILETIME format to Unix time.
1567 * If not NULL, 'remainder' contains the fractional part of the filetime,
1568 * in the range of [0..9999999] (even if time_t is negative).
1570 time_t DOSFS_FileTimeToUnixTime( const FILETIME *filetime, DWORD *remainder )
1572 /* Read the comment in the function DOSFS_UnixTimeToFileTime. */
1573 #if USE_LONG_LONG
1575 long long int t = filetime->dwHighDateTime;
1576 t <<= 32;
1577 t += (UINT32)filetime->dwLowDateTime;
1578 t -= 116444736000000000LL;
1579 if (t < 0)
1581 if (remainder) *remainder = 9999999 - (-t - 1) % 10000000;
1582 return -1 - ((-t - 1) / 10000000);
1584 else
1586 if (remainder) *remainder = t % 10000000;
1587 return t / 10000000;
1590 #else /* ISO version */
1592 UINT32 a0; /* 16 bit, low bits */
1593 UINT32 a1; /* 16 bit, medium bits */
1594 UINT32 a2; /* 32 bit, high bits */
1595 UINT32 r; /* remainder of division */
1596 unsigned int carry; /* carry bit for subtraction */
1597 int negative; /* whether a represents a negative value */
1599 /* Copy the time values to a2/a1/a0 */
1600 a2 = (UINT32)filetime->dwHighDateTime;
1601 a1 = ((UINT32)filetime->dwLowDateTime ) >> 16;
1602 a0 = ((UINT32)filetime->dwLowDateTime ) & 0xffff;
1604 /* Subtract the time difference */
1605 if (a0 >= 32768 ) a0 -= 32768 , carry = 0;
1606 else a0 += (1 << 16) - 32768 , carry = 1;
1608 if (a1 >= 54590 + carry) a1 -= 54590 + carry, carry = 0;
1609 else a1 += (1 << 16) - 54590 - carry, carry = 1;
1611 a2 -= 27111902 + carry;
1613 /* If a is negative, replace a by (-1-a) */
1614 negative = (a2 >= ((UINT32)1) << 31);
1615 if (negative)
1617 /* Set a to -a - 1 (a is a2/a1/a0) */
1618 a0 = 0xffff - a0;
1619 a1 = 0xffff - a1;
1620 a2 = ~a2;
1623 /* Divide a by 10000000 (a = a2/a1/a0), put the rest into r.
1624 Split the divisor into 10000 * 1000 which are both less than 0xffff. */
1625 a1 += (a2 % 10000) << 16;
1626 a2 /= 10000;
1627 a0 += (a1 % 10000) << 16;
1628 a1 /= 10000;
1629 r = a0 % 10000;
1630 a0 /= 10000;
1632 a1 += (a2 % 1000) << 16;
1633 a2 /= 1000;
1634 a0 += (a1 % 1000) << 16;
1635 a1 /= 1000;
1636 r += (a0 % 1000) * 10000;
1637 a0 /= 1000;
1639 /* If a was negative, replace a by (-1-a) and r by (9999999 - r) */
1640 if (negative)
1642 /* Set a to -a - 1 (a is a2/a1/a0) */
1643 a0 = 0xffff - a0;
1644 a1 = 0xffff - a1;
1645 a2 = ~a2;
1647 r = 9999999 - r;
1650 if (remainder) *remainder = r;
1652 /* Do not replace this by << 32, it gives a compiler warning and it does
1653 not work. */
1654 return ((((time_t)a2) << 16) << 16) + (a1 << 16) + a0;
1655 #endif
1659 /***********************************************************************
1660 * DosDateTimeToFileTime (KERNEL32.76)
1662 BOOL32 WINAPI DosDateTimeToFileTime( WORD fatdate, WORD fattime, LPFILETIME ft)
1664 struct tm newtm;
1666 newtm.tm_sec = (fattime & 0x1f) * 2;
1667 newtm.tm_min = (fattime >> 5) & 0x3f;
1668 newtm.tm_hour = (fattime >> 11);
1669 newtm.tm_mday = (fatdate & 0x1f);
1670 newtm.tm_mon = ((fatdate >> 5) & 0x0f) - 1;
1671 newtm.tm_year = (fatdate >> 9) + 80;
1672 DOSFS_UnixTimeToFileTime( mktime( &newtm ), ft, 0 );
1673 return TRUE;
1677 /***********************************************************************
1678 * FileTimeToDosDateTime (KERNEL32.111)
1680 BOOL32 WINAPI FileTimeToDosDateTime( const FILETIME *ft, LPWORD fatdate,
1681 LPWORD fattime )
1683 time_t unixtime = DOSFS_FileTimeToUnixTime( ft, NULL );
1684 struct tm *tm = localtime( &unixtime );
1685 if (fattime)
1686 *fattime = (tm->tm_hour << 11) + (tm->tm_min << 5) + (tm->tm_sec / 2);
1687 if (fatdate)
1688 *fatdate = ((tm->tm_year - 80) << 9) + ((tm->tm_mon + 1) << 5)
1689 + tm->tm_mday;
1690 return TRUE;
1694 /***********************************************************************
1695 * LocalFileTimeToFileTime (KERNEL32.373)
1697 BOOL32 WINAPI LocalFileTimeToFileTime( const FILETIME *localft,
1698 LPFILETIME utcft )
1700 struct tm *xtm;
1701 DWORD remainder;
1703 /* convert from local to UTC. Perhaps not correct. FIXME */
1704 time_t unixtime = DOSFS_FileTimeToUnixTime( localft, &remainder );
1705 xtm = gmtime( &unixtime );
1706 DOSFS_UnixTimeToFileTime( mktime(xtm), utcft, remainder );
1707 return TRUE;
1711 /***********************************************************************
1712 * FileTimeToLocalFileTime (KERNEL32.112)
1714 BOOL32 WINAPI FileTimeToLocalFileTime( const FILETIME *utcft,
1715 LPFILETIME localft )
1717 DWORD remainder;
1718 /* convert from UTC to local. Perhaps not correct. FIXME */
1719 time_t unixtime = DOSFS_FileTimeToUnixTime( utcft, &remainder );
1720 #ifdef HAVE_TIMEGM
1721 struct tm *xtm = localtime( &unixtime );
1722 time_t localtime;
1724 localtime = timegm(xtm);
1725 DOSFS_UnixTimeToFileTime( localtime, localft, remainder );
1727 #else
1728 struct tm *xtm,*gtm;
1729 time_t time1,time2;
1731 xtm = localtime( &unixtime );
1732 gtm = gmtime( &unixtime );
1733 time1 = mktime(xtm);
1734 time2 = mktime(gtm);
1735 DOSFS_UnixTimeToFileTime( 2*time1-time2, localft, remainder );
1736 #endif
1737 return TRUE;
1741 /***********************************************************************
1742 * FileTimeToSystemTime (KERNEL32.113)
1744 BOOL32 WINAPI FileTimeToSystemTime( const FILETIME *ft, LPSYSTEMTIME syst )
1746 struct tm *xtm;
1747 DWORD remainder;
1748 time_t xtime = DOSFS_FileTimeToUnixTime( ft, &remainder );
1749 xtm = gmtime(&xtime);
1750 syst->wYear = xtm->tm_year+1900;
1751 syst->wMonth = xtm->tm_mon + 1;
1752 syst->wDayOfWeek = xtm->tm_wday;
1753 syst->wDay = xtm->tm_mday;
1754 syst->wHour = xtm->tm_hour;
1755 syst->wMinute = xtm->tm_min;
1756 syst->wSecond = xtm->tm_sec;
1757 syst->wMilliseconds = remainder / 10000;
1758 return TRUE;
1761 /***********************************************************************
1762 * QueryDosDeviceA (KERNEL32.413)
1764 * returns array of strings terminated by \0, terminated by \0
1766 DWORD WINAPI QueryDosDevice32A(LPCSTR devname,LPSTR target,DWORD bufsize)
1768 LPSTR s;
1769 char buffer[200];
1771 TRACE(dosfs,"(%s,...)\n",devname?devname:"<null>");
1772 if (!devname) {
1773 /* return known MSDOS devices */
1774 lstrcpy32A(buffer,"CON COM1 COM2 LPT1 NUL ");
1775 while ((s=strchr(buffer,' ')))
1776 *s='\0';
1778 lstrcpyn32A(target,buffer,bufsize);
1779 return strlen(buffer);
1781 lstrcpy32A(buffer,"\\DEV\\");
1782 lstrcat32A(buffer,devname);
1783 if ((s=strchr(buffer,':'))) *s='\0';
1784 lstrcpyn32A(target,buffer,bufsize);
1785 return strlen(buffer);
1789 /***********************************************************************
1790 * QueryDosDeviceW (KERNEL32.414)
1792 * returns array of strings terminated by \0, terminated by \0
1794 DWORD WINAPI QueryDosDevice32W(LPCWSTR devname,LPWSTR target,DWORD bufsize)
1796 LPSTR devnameA = devname?HEAP_strdupWtoA(GetProcessHeap(),0,devname):NULL;
1797 LPSTR targetA = (LPSTR)HEAP_xalloc(GetProcessHeap(),0,bufsize);
1798 DWORD ret = QueryDosDevice32A(devnameA,targetA,bufsize);
1800 lstrcpynAtoW(target,targetA,bufsize);
1801 if (devnameA) HeapFree(GetProcessHeap(),0,devnameA);
1802 if (targetA) HeapFree(GetProcessHeap(),0,targetA);
1803 return ret;
1807 /***********************************************************************
1808 * SystemTimeToFileTime (KERNEL32.526)
1810 BOOL32 WINAPI SystemTimeToFileTime( const SYSTEMTIME *syst, LPFILETIME ft )
1812 #ifdef HAVE_TIMEGM
1813 struct tm xtm;
1814 time_t utctime;
1815 #else
1816 struct tm xtm,*local_tm,*utc_tm;
1817 time_t localtim,utctime;
1818 #endif
1820 xtm.tm_year = syst->wYear-1900;
1821 xtm.tm_mon = syst->wMonth - 1;
1822 xtm.tm_wday = syst->wDayOfWeek;
1823 xtm.tm_mday = syst->wDay;
1824 xtm.tm_hour = syst->wHour;
1825 xtm.tm_min = syst->wMinute;
1826 xtm.tm_sec = syst->wSecond; /* this is UTC */
1827 xtm.tm_isdst = -1;
1828 #ifdef HAVE_TIMEGM
1829 utctime = timegm(&xtm);
1830 DOSFS_UnixTimeToFileTime( utctime, ft,
1831 syst->wMilliseconds * 10000 );
1832 #else
1833 localtim = mktime(&xtm); /* now we've got local time */
1834 local_tm = localtime(&localtim);
1835 utc_tm = gmtime(&localtim);
1836 utctime = mktime(utc_tm);
1837 DOSFS_UnixTimeToFileTime( 2*localtim -utctime, ft,
1838 syst->wMilliseconds * 10000 );
1839 #endif
1840 return TRUE;
1843 BOOL32 WINAPI DefineDosDevice32A(DWORD flags,LPCSTR devname,LPCSTR targetpath) {
1844 FIXME(dosfs,"(0x%08lx,%s,%s),stub!\n",flags,devname,targetpath);
1845 SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
1846 return FALSE;