2 * DOS file system functions
4 * Copyright 1993 Erik Bos
5 * Copyright 1996 Alexandre Julliard
13 #ifdef HAVE_SYS_ERRNO_H
14 #include <sys/errno.h>
20 #include <sys/ioctl.h>
26 #include "wine/winbase16.h"
27 #include "wine/unicode.h"
28 #include "wine/winestring.h"
38 #include "debugtools.h"
40 DEFAULT_DEBUG_CHANNEL(dosfs);
41 DECLARE_DEBUG_CHANNEL(file);
43 /* Define the VFAT ioctl to get both short and long file names */
44 /* FIXME: is it possible to get this to work on other systems? */
46 /* We want the real kernel dirent structure, not the libc one */
51 unsigned short d_reclen;
55 #define VFAT_IOCTL_READDIR_BOTH _IOR('r', 1, KERNEL_DIRENT [2] )
58 #undef VFAT_IOCTL_READDIR_BOTH /* just in case... */
61 /* Chars we don't want to see in DOS file names */
62 #define INVALID_DOS_CHARS "*?<>|\"+=,;[] \345"
64 static const DOS_DEVICE DOSFS_Devices[] =
65 /* name, device flags (see Int 21/AX=0x4400) */
79 { "SCSIMGR$", 0xc0c0 },
83 #define GET_DRIVE(path) \
84 (((path)[1] == ':') ? toupper((path)[0]) - 'A' : DOSFS_CurDrive)
86 /* Directory info for DOSFS_ReadDir */
90 #ifdef VFAT_IOCTL_READDIR_BOTH
93 KERNEL_DIRENT dirent[2];
97 /* Info structure for FindFirstFile handle */
111 /***********************************************************************
114 * Return 1 if Unix file 'name' is also a valid MS-DOS name
115 * (i.e. contains only valid DOS chars, lower-case only, fits in 8.3 format).
116 * File name can be terminated by '\0', '\\' or '/'.
118 static int DOSFS_ValidDOSName( const char *name, int ignore_case )
120 static const char invalid_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" INVALID_DOS_CHARS;
121 const char *p = name;
122 const char *invalid = ignore_case ? (invalid_chars + 26) : invalid_chars;
127 /* Check for "." and ".." */
130 /* All other names beginning with '.' are invalid */
131 return (IS_END_OF_NAME(*p));
133 while (!IS_END_OF_NAME(*p))
135 if (strchr( invalid, *p )) return 0; /* Invalid char */
136 if (*p == '.') break; /* Start of the extension */
137 if (++len > 8) return 0; /* Name too long */
140 if (*p != '.') return 1; /* End of name */
142 if (IS_END_OF_NAME(*p)) return 0; /* Empty extension not allowed */
144 while (!IS_END_OF_NAME(*p))
146 if (strchr( invalid, *p )) return 0; /* Invalid char */
147 if (*p == '.') return 0; /* Second extension not allowed */
148 if (++len > 3) return 0; /* Extension too long */
155 /***********************************************************************
156 * DOSFS_ToDosFCBFormat
158 * Convert a file name to DOS FCB format (8+3 chars, padded with blanks),
159 * expanding wild cards and converting to upper-case in the process.
160 * File name can be terminated by '\0', '\\' or '/'.
161 * Return FALSE if the name is not a valid DOS name.
162 * 'buffer' must be at least 12 characters long.
164 BOOL DOSFS_ToDosFCBFormat( LPCSTR name, LPSTR buffer )
166 static const char invalid_chars[] = INVALID_DOS_CHARS;
167 const char *p = name;
170 /* Check for "." and ".." */
174 strcpy( buffer, ". " );
180 return (!*p || (*p == '/') || (*p == '\\'));
183 for (i = 0; i < 8; i++)
200 if (strchr( invalid_chars, *p )) return FALSE;
201 buffer[i] = toupper(*p);
209 /* Skip all chars after wildcard up to first dot */
210 while (*p && (*p != '/') && (*p != '\\') && (*p != '.')) p++;
214 /* Check if name too long */
215 if (*p && (*p != '/') && (*p != '\\') && (*p != '.')) return FALSE;
217 if (*p == '.') p++; /* Skip dot */
219 for (i = 8; i < 11; i++)
229 return FALSE; /* Second extension not allowed */
237 if (strchr( invalid_chars, *p )) return FALSE;
238 buffer[i] = toupper(*p);
245 /* at most 3 character of the extension are processed
246 * is something behind this ?
248 while (*p == '*' || *p == ' ') p++; /* skip wildcards and spaces */
249 return IS_END_OF_NAME(*p);
253 /***********************************************************************
254 * DOSFS_ToDosDTAFormat
256 * Convert a file name from FCB to DTA format (name.ext, null-terminated)
257 * converting to upper-case in the process.
258 * File name can be terminated by '\0', '\\' or '/'.
259 * 'buffer' must be at least 13 characters long.
261 static void DOSFS_ToDosDTAFormat( LPCSTR name, LPSTR buffer )
265 memcpy( buffer, name, 8 );
266 for (p = buffer + 8; (p > buffer) && (p[-1] == ' '); p--);
268 memcpy( p, name + 8, 3 );
269 for (p += 3; p[-1] == ' '; p--);
270 if (p[-1] == '.') p--;
275 /***********************************************************************
278 * Check a DOS file name against a mask (both in FCB format).
280 static int DOSFS_MatchShort( const char *mask, const char *name )
283 for (i = 11; i > 0; i--, mask++, name++)
284 if ((*mask != '?') && (*mask != *name)) return 0;
289 /***********************************************************************
292 * Check a long file name against a mask.
294 * Tests (done in W95 DOS shell - case insensitive):
295 * *.txt test1.test.txt *
297 * *.t??????.t* test1.ta.tornado.txt *
298 * *tornado* test1.ta.tornado.txt *
299 * t*t test1.ta.tornado.txt *
301 * ?est??? test1.txt -
302 * *test1.txt* test1.txt *
303 * h?l?o*t.dat hellothisisatest.dat *
305 static int DOSFS_MatchLong( const char *mask, const char *name,
308 const char *lastjoker = NULL;
309 const char *next_to_retry = NULL;
311 if (!strcmp( mask, "*.*" )) return 1;
312 while (*name && *mask)
317 while (*mask == '*') mask++; /* Skip consecutive '*' */
319 if (!*mask) return 1; /* end of mask is all '*', so match */
321 /* skip to the next match after the joker(s) */
322 if (case_sensitive) while (*name && (*name != *mask)) name++;
323 else while (*name && (toupper(*name) != toupper(*mask))) name++;
326 next_to_retry = name;
328 else if (*mask != '?')
333 if (*mask != *name) mismatch = 1;
337 if (toupper(*mask) != toupper(*name)) mismatch = 1;
351 else /* mismatch ! */
353 if (lastjoker) /* we had an '*', so we can try unlimitedly */
357 /* this scan sequence was a mismatch, so restart
358 * 1 char after the first char we checked last time */
360 name = next_to_retry;
363 return 0; /* bad luck */
372 while ((*mask == '.') || (*mask == '*'))
373 mask++; /* Ignore trailing '.' or '*' in mask */
374 return (!*name && !*mask);
378 /***********************************************************************
381 static DOS_DIR *DOSFS_OpenDir( LPCSTR path )
383 DOS_DIR *dir = HeapAlloc( GetProcessHeap(), 0, sizeof(*dir) );
386 SetLastError( ERROR_NOT_ENOUGH_MEMORY );
390 /* Treat empty path as root directory. This simplifies path split into
391 directory and mask in several other places */
392 if (!*path) path = "/";
394 #ifdef VFAT_IOCTL_READDIR_BOTH
396 /* Check if the VFAT ioctl is supported on this directory */
398 if ((dir->fd = open( path, O_RDONLY )) != -1)
400 if (ioctl( dir->fd, VFAT_IOCTL_READDIR_BOTH, (long)dir->dirent ) == -1)
407 /* Set the file pointer back at the start of the directory */
408 lseek( dir->fd, 0, SEEK_SET );
413 #endif /* VFAT_IOCTL_READDIR_BOTH */
415 /* Now use the standard opendir/readdir interface */
417 if (!(dir->dir = opendir( path )))
419 HeapFree( GetProcessHeap(), 0, dir );
426 /***********************************************************************
429 static void DOSFS_CloseDir( DOS_DIR *dir )
431 #ifdef VFAT_IOCTL_READDIR_BOTH
432 if (dir->fd != -1) close( dir->fd );
433 #endif /* VFAT_IOCTL_READDIR_BOTH */
434 if (dir->dir) closedir( dir->dir );
435 HeapFree( GetProcessHeap(), 0, dir );
439 /***********************************************************************
442 static BOOL DOSFS_ReadDir( DOS_DIR *dir, LPCSTR *long_name,
445 struct dirent *dirent;
447 #ifdef VFAT_IOCTL_READDIR_BOTH
450 if (ioctl( dir->fd, VFAT_IOCTL_READDIR_BOTH, (long)dir->dirent ) != -1) {
451 if (!dir->dirent[0].d_reclen) return FALSE;
452 if (!DOSFS_ToDosFCBFormat( dir->dirent[0].d_name, dir->short_name ))
453 dir->short_name[0] = '\0';
454 *short_name = dir->short_name;
455 if (dir->dirent[1].d_name[0]) *long_name = dir->dirent[1].d_name;
456 else *long_name = dir->dirent[0].d_name;
460 #endif /* VFAT_IOCTL_READDIR_BOTH */
462 if (!(dirent = readdir( dir->dir ))) return FALSE;
463 *long_name = dirent->d_name;
469 /***********************************************************************
472 * Transform a Unix file name into a hashed DOS name. If the name is a valid
473 * DOS name, it is converted to upper-case; otherwise it is replaced by a
474 * hashed version that fits in 8.3 format.
475 * File name can be terminated by '\0', '\\' or '/'.
476 * 'buffer' must be at least 13 characters long.
478 static void DOSFS_Hash( LPCSTR name, LPSTR buffer, BOOL dir_format,
481 static const char invalid_chars[] = INVALID_DOS_CHARS "~.";
482 static const char hash_chars[32] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345";
489 if (dir_format) strcpy( buffer, " " );
491 if (DOSFS_ValidDOSName( name, ignore_case ))
493 /* Check for '.' and '..' */
497 if (!dir_format) buffer[1] = buffer[2] = '\0';
498 if (name[1] == '.') buffer[1] = '.';
502 /* Simply copy the name, converting to uppercase */
504 for (dst = buffer; !IS_END_OF_NAME(*name) && (*name != '.'); name++)
505 *dst++ = toupper(*name);
508 if (dir_format) dst = buffer + 8;
510 for (name++; !IS_END_OF_NAME(*name); name++)
511 *dst++ = toupper(*name);
513 if (!dir_format) *dst = '\0';
517 /* Compute the hash code of the file name */
518 /* If you know something about hash functions, feel free to */
519 /* insert a better algorithm here... */
522 for (p = name, hash = 0xbeef; !IS_END_OF_NAME(p[1]); p++)
523 hash = (hash<<3) ^ (hash>>5) ^ tolower(*p) ^ (tolower(p[1]) << 8);
524 hash = (hash<<3) ^ (hash>>5) ^ tolower(*p); /* Last character*/
528 for (p = name, hash = 0xbeef; !IS_END_OF_NAME(p[1]); p++)
529 hash = (hash << 3) ^ (hash >> 5) ^ *p ^ (p[1] << 8);
530 hash = (hash << 3) ^ (hash >> 5) ^ *p; /* Last character */
533 /* Find last dot for start of the extension */
534 for (p = name+1, ext = NULL; !IS_END_OF_NAME(*p); p++)
535 if (*p == '.') ext = p;
536 if (ext && IS_END_OF_NAME(ext[1]))
537 ext = NULL; /* Empty extension ignored */
539 /* Copy first 4 chars, replacing invalid chars with '_' */
540 for (i = 4, p = name, dst = buffer; i > 0; i--, p++)
542 if (IS_END_OF_NAME(*p) || (p == ext)) break;
543 *dst++ = strchr( invalid_chars, *p ) ? '_' : toupper(*p);
545 /* Pad to 5 chars with '~' */
546 while (i-- >= 0) *dst++ = '~';
548 /* Insert hash code converted to 3 ASCII chars */
549 *dst++ = hash_chars[(hash >> 10) & 0x1f];
550 *dst++ = hash_chars[(hash >> 5) & 0x1f];
551 *dst++ = hash_chars[hash & 0x1f];
553 /* Copy the first 3 chars of the extension (if any) */
556 if (!dir_format) *dst++ = '.';
557 for (i = 3, ext++; (i > 0) && !IS_END_OF_NAME(*ext); i--, ext++)
558 *dst++ = strchr( invalid_chars, *ext ) ? '_' : toupper(*ext);
560 if (!dir_format) *dst = '\0';
564 /***********************************************************************
567 * Find the Unix file name in a given directory that corresponds to
568 * a file name (either in Unix or DOS format).
569 * File name can be terminated by '\0', '\\' or '/'.
570 * Return TRUE if OK, FALSE if no file name matches.
572 * 'long_buf' must be at least 'long_len' characters long. If the long name
573 * turns out to be larger than that, the function returns FALSE.
574 * 'short_buf' must be at least 13 characters long.
576 BOOL DOSFS_FindUnixName( LPCSTR path, LPCSTR name, LPSTR long_buf,
577 INT long_len, LPSTR short_buf, BOOL ignore_case)
580 LPCSTR long_name, short_name;
581 char dos_name[12], tmp_buf[13];
584 const char *p = strchr( name, '/' );
585 int len = p ? (int)(p - name) : strlen(name);
586 if ((p = strchr( name, '\\' ))) len = min( (int)(p - name), len );
587 /* Ignore trailing dots and spaces */
588 while (len > 1 && (name[len-1] == '.' || name[len-1] == ' ')) len--;
589 if (long_len < len + 1) return FALSE;
591 TRACE("%s,%s\n", path, name );
593 if (!DOSFS_ToDosFCBFormat( name, dos_name )) dos_name[0] = '\0';
595 if (!(dir = DOSFS_OpenDir( path )))
597 WARN("(%s,%s): can't open dir: %s\n",
598 path, name, strerror(errno) );
602 while ((ret = DOSFS_ReadDir( dir, &long_name, &short_name )))
604 /* Check against Unix name */
605 if (len == strlen(long_name))
609 if (!strncmp( long_name, name, len )) break;
613 if (!strncasecmp( long_name, name, len )) break;
618 /* Check against hashed DOS name */
621 DOSFS_Hash( long_name, tmp_buf, TRUE, ignore_case );
622 short_name = tmp_buf;
624 if (!strcmp( dos_name, short_name )) break;
629 if (long_buf) strcpy( long_buf, long_name );
633 DOSFS_ToDosDTAFormat( short_name, short_buf );
635 DOSFS_Hash( long_name, short_buf, FALSE, ignore_case );
637 TRACE("(%s,%s) -> %s (%s)\n",
638 path, name, long_name, short_buf ? short_buf : "***");
641 WARN("'%s' not found in '%s'\n", name, path);
642 DOSFS_CloseDir( dir );
647 /***********************************************************************
650 * Check if a DOS file name represents a DOS device and return the device.
652 const DOS_DEVICE *DOSFS_GetDevice( const char *name )
657 if (!name) return NULL; /* if FILE_DupUnixHandle was used */
658 if (name[0] && (name[1] == ':')) name += 2;
659 if ((p = strrchr( name, '/' ))) name = p + 1;
660 if ((p = strrchr( name, '\\' ))) name = p + 1;
661 for (i = 0; i < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0]); i++)
663 const char *dev = DOSFS_Devices[i].name;
664 if (!strncasecmp( dev, name, strlen(dev) ))
666 p = name + strlen( dev );
667 if (!*p || (*p == '.')) return &DOSFS_Devices[i];
674 /***********************************************************************
675 * DOSFS_GetDeviceByHandle
677 const DOS_DEVICE *DOSFS_GetDeviceByHandle( HFILE hFile )
679 const DOS_DEVICE *ret = NULL;
682 struct get_file_info_request *req = server_alloc_req( sizeof(*req), 0 );
685 if (!server_call( REQ_GET_FILE_INFO ) && (req->type == FILE_TYPE_UNKNOWN))
687 if ((req->attr >= 0) &&
688 (req->attr < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0])))
689 ret = &DOSFS_Devices[req->attr];
697 /***********************************************************************
700 * Open a DOS device. This might not map 1:1 into the UNIX device concept.
702 HFILE DOSFS_OpenDevice( const char *name, DWORD access )
708 if (!name) return (HFILE)NULL; /* if FILE_DupUnixHandle was used */
709 if (name[0] && (name[1] == ':')) name += 2;
710 if ((p = strrchr( name, '/' ))) name = p + 1;
711 if ((p = strrchr( name, '\\' ))) name = p + 1;
712 for (i = 0; i < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0]); i++)
714 const char *dev = DOSFS_Devices[i].name;
715 if (!strncasecmp( dev, name, strlen(dev) ))
717 p = name + strlen( dev );
718 if (!*p || (*p == '.')) {
720 if (!strcmp(DOSFS_Devices[i].name,"NUL"))
721 return FILE_CreateFile( "/dev/null", access,
722 FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,
723 OPEN_EXISTING, 0, -1, TRUE );
724 if (!strcmp(DOSFS_Devices[i].name,"CON")) {
726 switch (access & (GENERIC_READ|GENERIC_WRITE)) {
728 to_dup = GetStdHandle( STD_INPUT_HANDLE );
731 to_dup = GetStdHandle( STD_OUTPUT_HANDLE );
734 FIXME("can't open CON read/write\n");
738 if (!DuplicateHandle( GetCurrentProcess(), to_dup, GetCurrentProcess(),
739 &handle, 0, FALSE, DUPLICATE_SAME_ACCESS ))
740 handle = HFILE_ERROR;
743 if (!strcmp(DOSFS_Devices[i].name,"SCSIMGR$") ||
744 !strcmp(DOSFS_Devices[i].name,"HPSCAN"))
746 return FILE_CreateDevice( i, access, NULL );
749 if( (handle=COMM_CreatePort(name,access)) )
752 FIXME("device open %s not supported (yet)\n",DOSFS_Devices[i].name);
761 /***********************************************************************
764 * Get the drive specified by a given path name (DOS or Unix format).
766 static int DOSFS_GetPathDrive( const char **name )
769 const char *p = *name;
771 if (*p && (p[1] == ':'))
773 drive = toupper(*p) - 'A';
776 else if (*p == '/') /* Absolute Unix path? */
778 if ((drive = DRIVE_FindDriveRoot( name )) == -1)
780 MESSAGE("Warning: %s not accessible from a DOS drive\n", *name );
781 /* Assume it really was a DOS name */
782 drive = DRIVE_GetCurrentDrive();
785 else drive = DRIVE_GetCurrentDrive();
787 if (!DRIVE_IsValid(drive))
789 SetLastError( ERROR_INVALID_DRIVE );
796 /***********************************************************************
799 * Convert a file name (DOS or mixed DOS/Unix format) to a valid
800 * Unix name / short DOS name pair.
801 * Return FALSE if one of the path components does not exist. The last path
802 * component is only checked if 'check_last' is non-zero.
803 * The buffers pointed to by 'long_buf' and 'short_buf' must be
804 * at least MAX_PATHNAME_LEN long.
806 BOOL DOSFS_GetFullName( LPCSTR name, BOOL check_last, DOS_FULL_NAME *full )
810 char *p_l, *p_s, *root;
812 TRACE("%s (last=%d)\n", name, check_last );
814 if ((full->drive = DOSFS_GetPathDrive( &name )) == -1) return FALSE;
815 flags = DRIVE_GetFlags( full->drive );
817 lstrcpynA( full->long_name, DRIVE_GetRoot( full->drive ),
818 sizeof(full->long_name) );
819 if (full->long_name[1]) root = full->long_name + strlen(full->long_name);
820 else root = full->long_name; /* root directory */
822 strcpy( full->short_name, "A:\\" );
823 full->short_name[0] += full->drive;
825 if ((*name == '\\') || (*name == '/')) /* Absolute path */
827 while ((*name == '\\') || (*name == '/')) name++;
829 else /* Relative path */
831 lstrcpynA( root + 1, DRIVE_GetUnixCwd( full->drive ),
832 sizeof(full->long_name) - (root - full->long_name) - 1 );
833 if (root[1]) *root = '/';
834 lstrcpynA( full->short_name + 3, DRIVE_GetDosCwd( full->drive ),
835 sizeof(full->short_name) - 3 );
838 p_l = full->long_name[1] ? full->long_name + strlen(full->long_name)
840 p_s = full->short_name[3] ? full->short_name + strlen(full->short_name)
841 : full->short_name + 2;
844 while (*name && found)
846 /* Check for '.' and '..' */
850 if (IS_END_OF_NAME(name[1]))
853 while ((*name == '\\') || (*name == '/')) name++;
856 else if ((name[1] == '.') && IS_END_OF_NAME(name[2]))
859 while ((*name == '\\') || (*name == '/')) name++;
860 while ((p_l > root) && (*p_l != '/')) p_l--;
861 while ((p_s > full->short_name + 2) && (*p_s != '\\')) p_s--;
862 *p_l = *p_s = '\0'; /* Remove trailing separator */
867 /* Make sure buffers are large enough */
869 if ((p_s >= full->short_name + sizeof(full->short_name) - 14) ||
870 (p_l >= full->long_name + sizeof(full->long_name) - 1))
872 SetLastError( ERROR_PATH_NOT_FOUND );
876 /* Get the long and short name matching the file name */
878 if ((found = DOSFS_FindUnixName( full->long_name, name, p_l + 1,
879 sizeof(full->long_name) - (p_l - full->long_name) - 1,
880 p_s + 1, !(flags & DRIVE_CASE_SENSITIVE) )))
886 while (!IS_END_OF_NAME(*name)) name++;
888 else if (!check_last)
892 while (!IS_END_OF_NAME(*name) &&
893 (p_s < full->short_name + sizeof(full->short_name) - 1) &&
894 (p_l < full->long_name + sizeof(full->long_name) - 1))
896 *p_s++ = tolower(*name);
897 /* If the drive is case-sensitive we want to create new */
898 /* files in lower-case otherwise we can't reopen them */
899 /* under the same short name. */
900 if (flags & DRIVE_CASE_SENSITIVE) *p_l++ = tolower(*name);
904 /* Ignore trailing dots and spaces */
905 while(p_l[-1] == '.' || p_l[-1] == ' ') {
911 while ((*name == '\\') || (*name == '/')) name++;
918 SetLastError( ERROR_FILE_NOT_FOUND );
921 if (*name) /* Not last */
923 SetLastError( ERROR_PATH_NOT_FOUND );
927 if (!full->long_name[0]) strcpy( full->long_name, "/" );
928 if (!full->short_name[2]) strcpy( full->short_name + 2, "\\" );
929 TRACE("returning %s = %s\n", full->long_name, full->short_name );
934 /***********************************************************************
935 * GetShortPathNameA (KERNEL32.271)
939 * longpath=NULL: LastError=ERROR_INVALID_PARAMETER, ret=0
940 * *longpath="" or invalid: LastError=ERROR_BAD_PATHNAME, ret=0
942 * more observations ( with NT 3.51 (WinDD) ):
943 * longpath <= 8.3 -> just copy longpath to shortpath
945 * a) file does not exist -> return 0, LastError = ERROR_FILE_NOT_FOUND
946 * b) file does exist -> set the short filename.
947 * - trailing slashes are reproduced in the short name, even if the
948 * file is not a directory
949 * - the absolute/relative path of the short name is reproduced like found
951 * - longpath and shortpath may have the same adress
954 DWORD WINAPI GetShortPathNameA( LPCSTR longpath, LPSTR shortpath,
957 DOS_FULL_NAME full_name;
959 DWORD sp = 0, lp = 0;
963 TRACE("%s\n", debugstr_a(longpath));
966 SetLastError(ERROR_INVALID_PARAMETER);
970 SetLastError(ERROR_BAD_PATHNAME);
974 if ( ( tmpshortpath = HeapAlloc ( GetProcessHeap(), 0, MAX_PATHNAME_LEN ) ) == NULL ) {
975 SetLastError ( ERROR_NOT_ENOUGH_MEMORY );
979 /* check for drive letter */
980 if ( longpath[1] == ':' ) {
981 tmpshortpath[0] = longpath[0];
982 tmpshortpath[1] = ':';
986 if ( ( drive = DOSFS_GetPathDrive ( &longpath )) == -1 ) return 0;
987 flags = DRIVE_GetFlags ( drive );
989 while ( longpath[lp] ) {
991 /* check for path delimiters and reproduce them */
992 if ( longpath[lp] == '\\' || longpath[lp] == '/' ) {
993 if (!sp || tmpshortpath[sp-1]!= '\\')
995 /* strip double "\\" */
996 tmpshortpath[sp] = '\\';
999 tmpshortpath[sp]=0;/*terminate string*/
1004 tmplen = strcspn ( longpath + lp, "\\/" );
1005 lstrcpynA ( tmpshortpath+sp, longpath + lp, tmplen+1 );
1007 /* Check, if the current element is a valid dos name */
1008 if ( DOSFS_ValidDOSName ( longpath + lp, !(flags & DRIVE_CASE_SENSITIVE) ) ) {
1014 /* Check if the file exists and use the existing file name */
1015 if ( DOSFS_GetFullName ( tmpshortpath, TRUE, &full_name ) ) {
1016 strcpy( tmpshortpath+sp, strrchr ( full_name.short_name, '\\' ) + 1 );
1017 sp += strlen ( tmpshortpath+sp );
1022 TRACE("not found!\n" );
1023 SetLastError ( ERROR_FILE_NOT_FOUND );
1026 tmpshortpath[sp] = 0;
1028 lstrcpynA ( shortpath, tmpshortpath, shortlen );
1029 TRACE("returning %s\n", debugstr_a(shortpath) );
1030 tmplen = strlen ( tmpshortpath );
1031 HeapFree ( GetProcessHeap(), 0, tmpshortpath );
1037 /***********************************************************************
1038 * GetShortPathNameW (KERNEL32.272)
1040 DWORD WINAPI GetShortPathNameW( LPCWSTR longpath, LPWSTR shortpath,
1043 LPSTR longpathA, shortpathA;
1046 longpathA = HEAP_strdupWtoA( GetProcessHeap(), 0, longpath );
1047 shortpathA = HeapAlloc ( GetProcessHeap(), 0, shortlen );
1049 ret = GetShortPathNameA ( longpathA, shortpathA, shortlen );
1050 lstrcpynAtoW ( shortpath, shortpathA, shortlen );
1052 HeapFree( GetProcessHeap(), 0, longpathA );
1053 HeapFree( GetProcessHeap(), 0, shortpathA );
1059 /***********************************************************************
1060 * GetLongPathNameA (KERNEL32.xxx)
1062 DWORD WINAPI GetLongPathNameA( LPCSTR shortpath, LPSTR longpath,
1065 DOS_FULL_NAME full_name;
1066 char *p, *r, *ll, *ss;
1068 if (!DOSFS_GetFullName( shortpath, TRUE, &full_name )) return 0;
1069 lstrcpynA( longpath, full_name.short_name, longlen );
1071 /* Do some hackery to get the long filename. */
1074 ss=longpath+strlen(longpath);
1075 ll=full_name.long_name+strlen(full_name.long_name);
1077 while (ss>=longpath)
1079 /* FIXME: aren't we more paranoid, than needed? */
1080 while ((ss[0]=='\\') && (ss>=longpath)) ss--;
1082 while ((ss[0]!='\\') && (ss>=longpath)) ss--;
1085 /* FIXME: aren't we more paranoid, than needed? */
1086 while ((ll[0]=='/') && (ll>=full_name.long_name)) ll--;
1087 while ((ll[0]!='/') && (ll>=full_name.long_name)) ll--;
1088 if (ll<full_name.long_name)
1090 ERR("Bad longname! (ss=%s ll=%s)\n This should never happen !\n"
1097 /* FIXME: fix for names like "C:\\" (ie. with more '\'s) */
1101 if ((p-longpath)>0) longlen -= (p-longpath);
1102 lstrcpynA( p, ll , longlen);
1104 /* Now, change all '/' to '\' */
1105 for (r=p; r<(p+longlen); r++ )
1106 if (r[0]=='/') r[0]='\\';
1107 return strlen(longpath) - strlen(p) + longlen;
1111 return strlen(longpath);
1115 /***********************************************************************
1116 * GetLongPathNameW (KERNEL32.269)
1118 DWORD WINAPI GetLongPathNameW( LPCWSTR shortpath, LPWSTR longpath,
1121 DOS_FULL_NAME full_name;
1123 LPSTR shortpathA = HEAP_strdupWtoA( GetProcessHeap(), 0, shortpath );
1125 /* FIXME: is it correct to always return a fully qualified short path? */
1126 if (DOSFS_GetFullName( shortpathA, TRUE, &full_name ))
1128 ret = strlen( full_name.short_name );
1129 lstrcpynAtoW( longpath, full_name.long_name, longlen );
1131 HeapFree( GetProcessHeap(), 0, shortpathA );
1136 /***********************************************************************
1137 * DOSFS_DoGetFullPathName
1139 * Implementation of GetFullPathNameA/W.
1141 * bon@elektron 000331:
1142 * A test for GetFullPathName with many patholotical case
1143 * gives now identical output for Wine and OSR2
1145 static DWORD DOSFS_DoGetFullPathName( LPCSTR name, DWORD len, LPSTR result,
1149 DOS_FULL_NAME full_name;
1152 char drivecur[]="c:.";
1154 int namelen,drive=0;
1156 if ((strlen(name) >1)&& (name[1]==':'))
1157 /*drive letter given */
1159 driveletter = name[0];
1161 if ((strlen(name) >2)&& (name[1]==':') &&
1162 ((name[2]=='\\') || (name[2]=='/')))
1163 /*absolute path given */
1165 lstrcpynA(full_name.short_name,name,MAX_PATHNAME_LEN);
1166 drive = (int)toupper(name[0]) - 'A';
1171 drivecur[0]=driveletter;
1173 strcpy(drivecur,".");
1174 if (!DOSFS_GetFullName( drivecur, FALSE, &full_name ))
1176 FIXME("internal: error getting drive/path\n");
1179 /* find path that drive letter substitutes*/
1180 drive = (int)toupper(full_name.short_name[0]) -0x41;
1181 root= DRIVE_GetRoot(drive);
1184 FIXME("internal: error getting DOS Drive Root\n");
1187 p= full_name.long_name +strlen(root);
1188 /* append long name (= unix name) to drive */
1189 lstrcpynA(full_name.short_name+2,p,MAX_PATHNAME_LEN-3);
1190 /* append name to treat */
1191 namelen= strlen(full_name.short_name);
1194 p += +2; /* skip drive name when appending */
1195 if (namelen +2 + strlen(p) > MAX_PATHNAME_LEN)
1197 FIXME("internal error: buffer too small\n");
1200 full_name.short_name[namelen++] ='\\';
1201 full_name.short_name[namelen] = 0;
1202 lstrcpynA(full_name.short_name +namelen,p,MAX_PATHNAME_LEN-namelen);
1204 /* reverse all slashes */
1205 for (p=full_name.short_name;
1206 p < full_name.short_name+strlen(full_name.short_name);
1212 /* Use memmove, as areas overlap*/
1214 while ((p = strstr(full_name.short_name,"\\..\\")))
1216 if (p > full_name.short_name+2)
1219 q = strrchr(full_name.short_name,'\\');
1220 memmove(q+1,p+4,strlen(p+4)+1);
1224 memmove(full_name.short_name+3,p+4,strlen(p+4)+1);
1227 if ((full_name.short_name[2]=='.')&&(full_name.short_name[3]=='.'))
1229 /* This case istn't treated yet : c:..\test */
1230 memmove(full_name.short_name+2,full_name.short_name+4,
1231 strlen(full_name.short_name+4)+1);
1234 while ((p = strstr(full_name.short_name,"\\.\\")))
1237 memmove(p+1,p+3,strlen(p+3)+1);
1239 if (!(DRIVE_GetFlags(drive) & DRIVE_CASE_PRESERVING))
1240 _strupr( full_name.short_name );
1241 namelen=strlen(full_name.short_name);
1242 if (!strcmp(full_name.short_name+namelen-3,"\\.."))
1244 /* one more starnge case: "c:\test\test1\.."
1246 *(full_name.short_name+namelen-3)=0;
1247 q = strrchr(full_name.short_name,'\\');
1250 if (full_name.short_name[namelen-1]=='.')
1251 full_name.short_name[(namelen--)-1] =0;
1253 if (full_name.short_name[namelen-1]=='\\')
1254 full_name.short_name[(namelen--)-1] =0;
1255 TRACE("got %s\n",full_name.short_name);
1257 /* If the lpBuffer buffer is too small, the return value is the
1258 size of the buffer, in characters, required to hold the path
1259 plus the terminating \0 (tested against win95osr, bon 001118)
1261 ret = strlen(full_name.short_name);
1264 /* don't touch anything when the buffer is not large enough */
1265 SetLastError( ERROR_INSUFFICIENT_BUFFER );
1271 lstrcpynAtoW( (LPWSTR)result, full_name.short_name, len );
1273 lstrcpynA( result, full_name.short_name, len );
1276 TRACE("returning '%s'\n", full_name.short_name );
1281 /***********************************************************************
1282 * GetFullPathNameA (KERNEL32.272)
1284 * if the path closed with '\', *lastpart is 0
1286 DWORD WINAPI GetFullPathNameA( LPCSTR name, DWORD len, LPSTR buffer,
1289 DWORD ret = DOSFS_DoGetFullPathName( name, len, buffer, FALSE );
1290 if (ret && (ret<=len) && buffer && lastpart)
1292 LPSTR p = buffer + strlen(buffer);
1296 while ((p > buffer + 2) && (*p != '\\')) p--;
1299 else *lastpart = NULL;
1305 /***********************************************************************
1306 * GetFullPathNameW (KERNEL32.273)
1308 DWORD WINAPI GetFullPathNameW( LPCWSTR name, DWORD len, LPWSTR buffer,
1311 LPSTR nameA = HEAP_strdupWtoA( GetProcessHeap(), 0, name );
1312 DWORD ret = DOSFS_DoGetFullPathName( nameA, len, (LPSTR)buffer, TRUE );
1313 HeapFree( GetProcessHeap(), 0, nameA );
1314 if (ret && (ret<=len) && buffer && lastpart)
1316 LPWSTR p = buffer + strlenW(buffer);
1317 if (*p != (WCHAR)'\\')
1319 while ((p > buffer + 2) && (*p != (WCHAR)'\\')) p--;
1322 else *lastpart = NULL;
1327 /***********************************************************************
1330 static int DOSFS_FindNextEx( FIND_FIRST_INFO *info, WIN32_FIND_DATAA *entry )
1332 BYTE attr = info->attr | FA_UNUSED | FA_ARCHIVE | FA_RDONLY;
1333 UINT flags = DRIVE_GetFlags( info->drive );
1334 char *p, buffer[MAX_PATHNAME_LEN];
1335 const char *drive_path;
1337 LPCSTR long_name, short_name;
1338 BY_HANDLE_FILE_INFORMATION fileinfo;
1341 if ((info->attr & ~(FA_UNUSED | FA_ARCHIVE | FA_RDONLY)) == FA_LABEL)
1343 if (info->cur_pos) return 0;
1344 entry->dwFileAttributes = FILE_ATTRIBUTE_LABEL;
1345 RtlSecondsSince1970ToTime( (time_t)0, &entry->ftCreationTime );
1346 RtlSecondsSince1970ToTime( (time_t)0, &entry->ftLastAccessTime );
1347 RtlSecondsSince1970ToTime( (time_t)0, &entry->ftLastWriteTime );
1348 entry->nFileSizeHigh = 0;
1349 entry->nFileSizeLow = 0;
1350 entry->dwReserved0 = 0;
1351 entry->dwReserved1 = 0;
1352 DOSFS_ToDosDTAFormat( DRIVE_GetLabel( info->drive ), entry->cFileName );
1353 strcpy( entry->cAlternateFileName, entry->cFileName );
1355 TRACE("returning %s (%s) as label\n",
1356 entry->cFileName, entry->cAlternateFileName);
1360 drive_path = info->path + strlen(DRIVE_GetRoot( info->drive ));
1361 while ((*drive_path == '/') || (*drive_path == '\\')) drive_path++;
1362 drive_root = !*drive_path;
1364 lstrcpynA( buffer, info->path, sizeof(buffer) - 1 );
1365 strcat( buffer, "/" );
1366 p = buffer + strlen(buffer);
1368 while (DOSFS_ReadDir( info->dir, &long_name, &short_name ))
1372 /* Don't return '.' and '..' in the root of the drive */
1373 if (drive_root && (long_name[0] == '.') &&
1374 (!long_name[1] || ((long_name[1] == '.') && !long_name[2])))
1377 /* Check the long mask */
1379 if (info->long_mask)
1381 if (!DOSFS_MatchLong( info->long_mask, long_name,
1382 flags & DRIVE_CASE_SENSITIVE )) continue;
1385 /* Check the short mask */
1387 if (info->short_mask)
1391 DOSFS_Hash( long_name, dos_name, TRUE,
1392 !(flags & DRIVE_CASE_SENSITIVE) );
1393 short_name = dos_name;
1395 if (!DOSFS_MatchShort( info->short_mask, short_name )) continue;
1398 /* Check the file attributes */
1400 lstrcpynA( p, long_name, sizeof(buffer) - (int)(p - buffer) );
1401 if (!FILE_Stat( buffer, &fileinfo ))
1403 WARN("can't stat %s\n", buffer);
1406 if (fileinfo.dwFileAttributes & ~attr) continue;
1408 /* We now have a matching entry; fill the result and return */
1410 entry->dwFileAttributes = fileinfo.dwFileAttributes;
1411 entry->ftCreationTime = fileinfo.ftCreationTime;
1412 entry->ftLastAccessTime = fileinfo.ftLastAccessTime;
1413 entry->ftLastWriteTime = fileinfo.ftLastWriteTime;
1414 entry->nFileSizeHigh = fileinfo.nFileSizeHigh;
1415 entry->nFileSizeLow = fileinfo.nFileSizeLow;
1418 DOSFS_ToDosDTAFormat( short_name, entry->cAlternateFileName );
1420 DOSFS_Hash( long_name, entry->cAlternateFileName, FALSE,
1421 !(flags & DRIVE_CASE_SENSITIVE) );
1423 lstrcpynA( entry->cFileName, long_name, sizeof(entry->cFileName) );
1424 if (!(flags & DRIVE_CASE_PRESERVING)) _strlwr( entry->cFileName );
1425 TRACE("returning %s (%s) %02lx %ld\n",
1426 entry->cFileName, entry->cAlternateFileName,
1427 entry->dwFileAttributes, entry->nFileSizeLow );
1430 return 0; /* End of directory */
1433 /***********************************************************************
1436 * Find the next matching file. Return the number of entries read to find
1437 * the matching one, or 0 if no more entries.
1438 * 'short_mask' is the 8.3 mask (in FCB format), 'long_mask' is the long
1439 * file name mask. Either or both can be NULL.
1441 * NOTE: This is supposed to be only called by the int21 emulation
1442 * routines. Thus, we should own the Win16Mutex anyway.
1443 * Nevertheless, we explicitly enter it to ensure the static
1444 * directory cache is protected.
1446 int DOSFS_FindNext( const char *path, const char *short_mask,
1447 const char *long_mask, int drive, BYTE attr,
1448 int skip, WIN32_FIND_DATAA *entry )
1450 static FIND_FIRST_INFO info = { NULL };
1451 LPCSTR short_name, long_name;
1454 SYSLEVEL_EnterWin16Lock();
1456 /* Check the cached directory */
1457 if (!(info.dir && info.path == path && info.short_mask == short_mask
1458 && info.long_mask == long_mask && info.drive == drive
1459 && info.attr == attr && info.cur_pos <= skip))
1461 /* Not in the cache, open it anew */
1462 if (info.dir) DOSFS_CloseDir( info.dir );
1464 info.path = (LPSTR)path;
1465 info.long_mask = (LPSTR)long_mask;
1466 info.short_mask = (LPSTR)short_mask;
1470 info.dir = DOSFS_OpenDir( info.path );
1473 /* Skip to desired position */
1474 while (info.cur_pos < skip)
1475 if (info.dir && DOSFS_ReadDir( info.dir, &long_name, &short_name ))
1480 if (info.dir && info.cur_pos == skip && DOSFS_FindNextEx( &info, entry ))
1481 count = info.cur_pos - skip;
1487 if (info.dir) DOSFS_CloseDir( info.dir );
1488 memset( &info, '\0', sizeof(info) );
1491 SYSLEVEL_LeaveWin16Lock();
1496 /*************************************************************************
1497 * FindFirstFileExA (KERNEL32)
1499 HANDLE WINAPI FindFirstFileExA(
1501 FINDEX_INFO_LEVELS fInfoLevelId,
1502 LPVOID lpFindFileData,
1503 FINDEX_SEARCH_OPS fSearchOp,
1504 LPVOID lpSearchFilter,
1505 DWORD dwAdditionalFlags)
1507 DOS_FULL_NAME full_name;
1509 FIND_FIRST_INFO *info;
1511 if ((fSearchOp != FindExSearchNameMatch) || (dwAdditionalFlags != 0))
1513 FIXME("options not implemented 0x%08x 0x%08lx\n", fSearchOp, dwAdditionalFlags );
1514 return INVALID_HANDLE_VALUE;
1517 switch(fInfoLevelId)
1519 case FindExInfoStandard:
1521 WIN32_FIND_DATAA * data = (WIN32_FIND_DATAA *) lpFindFileData;
1522 data->dwReserved0 = data->dwReserved1 = 0x0;
1523 if (!lpFileName) return 0;
1524 if (!DOSFS_GetFullName( lpFileName, FALSE, &full_name )) break;
1525 if (!(handle = GlobalAlloc(GMEM_MOVEABLE, sizeof(FIND_FIRST_INFO)))) break;
1526 info = (FIND_FIRST_INFO *)GlobalLock( handle );
1527 info->path = HEAP_strdupA( GetProcessHeap(), 0, full_name.long_name );
1528 info->long_mask = strrchr( info->path, '/' );
1529 *(info->long_mask++) = '\0';
1530 info->short_mask = NULL;
1532 if (lpFileName[0] && (lpFileName[1] == ':'))
1533 info->drive = toupper(*lpFileName) - 'A';
1534 else info->drive = DRIVE_GetCurrentDrive();
1537 info->dir = DOSFS_OpenDir( info->path );
1539 GlobalUnlock( handle );
1540 if (!FindNextFileA( handle, data ))
1542 FindClose( handle );
1543 SetLastError( ERROR_NO_MORE_FILES );
1550 FIXME("fInfoLevelId 0x%08x not implemented\n", fInfoLevelId );
1552 return INVALID_HANDLE_VALUE;
1555 /*************************************************************************
1556 * FindFirstFileA (KERNEL32.123)
1558 HANDLE WINAPI FindFirstFileA(
1560 WIN32_FIND_DATAA *lpFindData )
1562 return FindFirstFileExA(lpFileName, FindExInfoStandard, lpFindData,
1563 FindExSearchNameMatch, NULL, 0);
1566 /*************************************************************************
1567 * FindFirstFileExW (KERNEL32)
1569 HANDLE WINAPI FindFirstFileExW(
1571 FINDEX_INFO_LEVELS fInfoLevelId,
1572 LPVOID lpFindFileData,
1573 FINDEX_SEARCH_OPS fSearchOp,
1574 LPVOID lpSearchFilter,
1575 DWORD dwAdditionalFlags)
1578 WIN32_FIND_DATAA dataA;
1579 LPVOID _lpFindFileData;
1582 switch(fInfoLevelId)
1584 case FindExInfoStandard:
1586 _lpFindFileData = &dataA;
1590 FIXME("fInfoLevelId 0x%08x not implemented\n", fInfoLevelId );
1591 return INVALID_HANDLE_VALUE;
1594 pathA = HEAP_strdupWtoA( GetProcessHeap(), 0, lpFileName );
1595 handle = FindFirstFileExA(pathA, fInfoLevelId, _lpFindFileData, fSearchOp, lpSearchFilter, dwAdditionalFlags);
1596 HeapFree( GetProcessHeap(), 0, pathA );
1597 if (handle == INVALID_HANDLE_VALUE) return handle;
1599 switch(fInfoLevelId)
1601 case FindExInfoStandard:
1603 WIN32_FIND_DATAW *dataW = (WIN32_FIND_DATAW*) lpFindFileData;
1604 dataW->dwFileAttributes = dataA.dwFileAttributes;
1605 dataW->ftCreationTime = dataA.ftCreationTime;
1606 dataW->ftLastAccessTime = dataA.ftLastAccessTime;
1607 dataW->ftLastWriteTime = dataA.ftLastWriteTime;
1608 dataW->nFileSizeHigh = dataA.nFileSizeHigh;
1609 dataW->nFileSizeLow = dataA.nFileSizeLow;
1610 MultiByteToWideChar( CP_ACP, 0, dataA.cFileName, -1,
1611 dataW->cFileName, sizeof(dataW->cFileName)/sizeof(WCHAR) );
1612 MultiByteToWideChar( CP_ACP, 0, dataA.cAlternateFileName, -1,
1613 dataW->cAlternateFileName,
1614 sizeof(dataW->cAlternateFileName)/sizeof(WCHAR) );
1618 FIXME("fInfoLevelId 0x%08x not implemented\n", fInfoLevelId );
1619 return INVALID_HANDLE_VALUE;
1624 /*************************************************************************
1625 * FindFirstFileW (KERNEL32.124)
1627 HANDLE WINAPI FindFirstFileW( LPCWSTR lpFileName, WIN32_FIND_DATAW *lpFindData )
1629 return FindFirstFileExW(lpFileName, FindExInfoStandard, lpFindData,
1630 FindExSearchNameMatch, NULL, 0);
1633 /*************************************************************************
1634 * FindNextFileA (KERNEL32.126)
1636 BOOL WINAPI FindNextFileA( HANDLE handle, WIN32_FIND_DATAA *data )
1638 FIND_FIRST_INFO *info;
1640 if ((handle == INVALID_HANDLE_VALUE) ||
1641 !(info = (FIND_FIRST_INFO *)GlobalLock( handle )))
1643 SetLastError( ERROR_INVALID_HANDLE );
1646 GlobalUnlock( handle );
1647 if (!info->path || !info->dir)
1649 SetLastError( ERROR_NO_MORE_FILES );
1652 if (!DOSFS_FindNextEx( info, data ))
1654 DOSFS_CloseDir( info->dir ); info->dir = NULL;
1655 HeapFree( GetProcessHeap(), 0, info->path );
1656 info->path = info->long_mask = NULL;
1657 SetLastError( ERROR_NO_MORE_FILES );
1664 /*************************************************************************
1665 * FindNextFileW (KERNEL32.127)
1667 BOOL WINAPI FindNextFileW( HANDLE handle, WIN32_FIND_DATAW *data )
1669 WIN32_FIND_DATAA dataA;
1670 if (!FindNextFileA( handle, &dataA )) return FALSE;
1671 data->dwFileAttributes = dataA.dwFileAttributes;
1672 data->ftCreationTime = dataA.ftCreationTime;
1673 data->ftLastAccessTime = dataA.ftLastAccessTime;
1674 data->ftLastWriteTime = dataA.ftLastWriteTime;
1675 data->nFileSizeHigh = dataA.nFileSizeHigh;
1676 data->nFileSizeLow = dataA.nFileSizeLow;
1677 MultiByteToWideChar( CP_ACP, 0, dataA.cFileName, -1,
1678 data->cFileName, sizeof(data->cFileName)/sizeof(WCHAR) );
1679 MultiByteToWideChar( CP_ACP, 0, dataA.cAlternateFileName, -1,
1680 data->cAlternateFileName,
1681 sizeof(data->cAlternateFileName)/sizeof(WCHAR) );
1685 /*************************************************************************
1686 * FindClose (KERNEL32.119)
1688 BOOL WINAPI FindClose( HANDLE handle )
1690 FIND_FIRST_INFO *info;
1692 if ((handle == INVALID_HANDLE_VALUE) ||
1693 !(info = (FIND_FIRST_INFO *)GlobalLock( handle )))
1695 SetLastError( ERROR_INVALID_HANDLE );
1698 if (info->dir) DOSFS_CloseDir( info->dir );
1699 if (info->path) HeapFree( GetProcessHeap(), 0, info->path );
1700 GlobalUnlock( handle );
1701 GlobalFree( handle );
1705 /***********************************************************************
1706 * DOSFS_UnixTimeToFileTime
1708 * Convert a Unix time to FILETIME format.
1709 * The FILETIME structure is a 64-bit value representing the number of
1710 * 100-nanosecond intervals since January 1, 1601, 0:00.
1711 * 'remainder' is the nonnegative number of 100-ns intervals
1712 * corresponding to the time fraction smaller than 1 second that
1713 * couldn't be stored in the time_t value.
1715 void DOSFS_UnixTimeToFileTime( time_t unix_time, FILETIME *filetime,
1721 The time difference between 1 January 1601, 00:00:00 and
1722 1 January 1970, 00:00:00 is 369 years, plus the leap years
1723 from 1604 to 1968, excluding 1700, 1800, 1900.
1724 This makes (1968 - 1600) / 4 - 3 = 89 leap days, and a total
1727 Any day in that period had 24 * 60 * 60 = 86400 seconds.
1729 The time difference is 134774 * 86400 * 10000000, which can be written
1731 27111902 * 2^32 + 3577643008
1732 413 * 2^48 + 45534 * 2^32 + 54590 * 2^16 + 32768
1734 If you find that these constants are buggy, please change them in all
1735 instances in both conversion functions.
1738 There are two versions, one of them uses long long variables and
1739 is presumably faster but not ISO C. The other one uses standard C
1740 data types and operations but relies on the assumption that negative
1741 numbers are stored as 2's complement (-1 is 0xffff....). If this
1742 assumption is violated, dates before 1970 will not convert correctly.
1743 This should however work on any reasonable architecture where WINE
1748 Take care not to remove the casts. I have tested these functions
1749 (in both versions) for a lot of numbers. I would be interested in
1750 results on other compilers than GCC.
1752 The operations have been designed to account for the possibility
1753 of 64-bit time_t in future UNICES. Even the versions without
1754 internal long long numbers will work if time_t only is 64 bit.
1755 A 32-bit shift, which was necessary for that operation, turned out
1756 not to work correctly in GCC, besides giving the warning. So I
1757 used a double 16-bit shift instead. Numbers are in the ISO version
1758 represented by three limbs, the most significant with 32 bit, the
1759 other two with 16 bit each.
1761 As the modulo-operator % is not well-defined for negative numbers,
1762 negative divisors have been avoided in DOSFS_FileTimeToUnixTime.
1764 There might be quicker ways to do this in C. Certainly so in
1767 Claus Fischer, fischer@iue.tuwien.ac.at
1770 #if SIZEOF_LONG_LONG >= 8
1771 # define USE_LONG_LONG 1
1773 # define USE_LONG_LONG 0
1776 #if USE_LONG_LONG /* gcc supports long long type */
1778 long long int t = unix_time;
1780 t += 116444736000000000LL;
1782 filetime->dwLowDateTime = (UINT)t;
1783 filetime->dwHighDateTime = (UINT)(t >> 32);
1785 #else /* ISO version */
1787 UINT a0; /* 16 bit, low bits */
1788 UINT a1; /* 16 bit, medium bits */
1789 UINT a2; /* 32 bit, high bits */
1791 /* Copy the unix time to a2/a1/a0 */
1792 a0 = unix_time & 0xffff;
1793 a1 = (unix_time >> 16) & 0xffff;
1794 /* This is obsolete if unix_time is only 32 bits, but it does not hurt.
1795 Do not replace this by >> 32, it gives a compiler warning and it does
1797 a2 = (unix_time >= 0 ? (unix_time >> 16) >> 16 :
1798 ~((~unix_time >> 16) >> 16));
1800 /* Multiply a by 10000000 (a = a2/a1/a0)
1801 Split the factor into 10000 * 1000 which are both less than 0xffff. */
1803 a1 = a1 * 10000 + (a0 >> 16);
1804 a2 = a2 * 10000 + (a1 >> 16);
1809 a1 = a1 * 1000 + (a0 >> 16);
1810 a2 = a2 * 1000 + (a1 >> 16);
1814 /* Add the time difference and the remainder */
1815 a0 += 32768 + (remainder & 0xffff);
1816 a1 += 54590 + (remainder >> 16 ) + (a0 >> 16);
1817 a2 += 27111902 + (a1 >> 16);
1822 filetime->dwLowDateTime = (a1 << 16) + a0;
1823 filetime->dwHighDateTime = a2;
1828 /***********************************************************************
1829 * DOSFS_FileTimeToUnixTime
1831 * Convert a FILETIME format to Unix time.
1832 * If not NULL, 'remainder' contains the fractional part of the filetime,
1833 * in the range of [0..9999999] (even if time_t is negative).
1835 time_t DOSFS_FileTimeToUnixTime( const FILETIME *filetime, DWORD *remainder )
1837 /* Read the comment in the function DOSFS_UnixTimeToFileTime. */
1840 long long int t = filetime->dwHighDateTime;
1842 t += (UINT)filetime->dwLowDateTime;
1843 t -= 116444736000000000LL;
1846 if (remainder) *remainder = 9999999 - (-t - 1) % 10000000;
1847 return -1 - ((-t - 1) / 10000000);
1851 if (remainder) *remainder = t % 10000000;
1852 return t / 10000000;
1855 #else /* ISO version */
1857 UINT a0; /* 16 bit, low bits */
1858 UINT a1; /* 16 bit, medium bits */
1859 UINT a2; /* 32 bit, high bits */
1860 UINT r; /* remainder of division */
1861 unsigned int carry; /* carry bit for subtraction */
1862 int negative; /* whether a represents a negative value */
1864 /* Copy the time values to a2/a1/a0 */
1865 a2 = (UINT)filetime->dwHighDateTime;
1866 a1 = ((UINT)filetime->dwLowDateTime ) >> 16;
1867 a0 = ((UINT)filetime->dwLowDateTime ) & 0xffff;
1869 /* Subtract the time difference */
1870 if (a0 >= 32768 ) a0 -= 32768 , carry = 0;
1871 else a0 += (1 << 16) - 32768 , carry = 1;
1873 if (a1 >= 54590 + carry) a1 -= 54590 + carry, carry = 0;
1874 else a1 += (1 << 16) - 54590 - carry, carry = 1;
1876 a2 -= 27111902 + carry;
1878 /* If a is negative, replace a by (-1-a) */
1879 negative = (a2 >= ((UINT)1) << 31);
1882 /* Set a to -a - 1 (a is a2/a1/a0) */
1888 /* Divide a by 10000000 (a = a2/a1/a0), put the rest into r.
1889 Split the divisor into 10000 * 1000 which are both less than 0xffff. */
1890 a1 += (a2 % 10000) << 16;
1892 a0 += (a1 % 10000) << 16;
1897 a1 += (a2 % 1000) << 16;
1899 a0 += (a1 % 1000) << 16;
1901 r += (a0 % 1000) * 10000;
1904 /* If a was negative, replace a by (-1-a) and r by (9999999 - r) */
1907 /* Set a to -a - 1 (a is a2/a1/a0) */
1915 if (remainder) *remainder = r;
1917 /* Do not replace this by << 32, it gives a compiler warning and it does
1919 return ((((time_t)a2) << 16) << 16) + (a1 << 16) + a0;
1924 /***********************************************************************
1925 * MulDiv (KERNEL32.391)
1927 * Result of multiplication and division
1928 * -1: Overflow occurred or Divisor was 0
1935 #if SIZEOF_LONG_LONG >= 8
1938 if (!nDivisor) return -1;
1940 /* We want to deal with a positive divisor to simplify the logic. */
1943 nMultiplicand = - nMultiplicand;
1944 nDivisor = -nDivisor;
1947 /* If the result is positive, we "add" to round. else, we subtract to round. */
1948 if ( ( (nMultiplicand < 0) && (nMultiplier < 0) ) ||
1949 ( (nMultiplicand >= 0) && (nMultiplier >= 0) ) )
1950 ret = (((long long)nMultiplicand * nMultiplier) + (nDivisor/2)) / nDivisor;
1952 ret = (((long long)nMultiplicand * nMultiplier) - (nDivisor/2)) / nDivisor;
1954 if ((ret > 2147483647) || (ret < -2147483647)) return -1;
1957 if (!nDivisor) return -1;
1959 /* We want to deal with a positive divisor to simplify the logic. */
1962 nMultiplicand = - nMultiplicand;
1963 nDivisor = -nDivisor;
1966 /* If the result is positive, we "add" to round. else, we subtract to round. */
1967 if ( ( (nMultiplicand < 0) && (nMultiplier < 0) ) ||
1968 ( (nMultiplicand >= 0) && (nMultiplier >= 0) ) )
1969 return ((nMultiplicand * nMultiplier) + (nDivisor/2)) / nDivisor;
1971 return ((nMultiplicand * nMultiplier) - (nDivisor/2)) / nDivisor;
1977 /***********************************************************************
1978 * DosDateTimeToFileTime (KERNEL32.76)
1980 BOOL WINAPI DosDateTimeToFileTime( WORD fatdate, WORD fattime, LPFILETIME ft)
1984 newtm.tm_sec = (fattime & 0x1f) * 2;
1985 newtm.tm_min = (fattime >> 5) & 0x3f;
1986 newtm.tm_hour = (fattime >> 11);
1987 newtm.tm_mday = (fatdate & 0x1f);
1988 newtm.tm_mon = ((fatdate >> 5) & 0x0f) - 1;
1989 newtm.tm_year = (fatdate >> 9) + 80;
1990 RtlSecondsSince1970ToTime( mktime( &newtm ), ft );
1995 /***********************************************************************
1996 * FileTimeToDosDateTime (KERNEL32.111)
1998 BOOL WINAPI FileTimeToDosDateTime( const FILETIME *ft, LPWORD fatdate,
2001 time_t unixtime = DOSFS_FileTimeToUnixTime( ft, NULL );
2002 struct tm *tm = localtime( &unixtime );
2004 *fattime = (tm->tm_hour << 11) + (tm->tm_min << 5) + (tm->tm_sec / 2);
2006 *fatdate = ((tm->tm_year - 80) << 9) + ((tm->tm_mon + 1) << 5)
2012 /***********************************************************************
2013 * LocalFileTimeToFileTime (KERNEL32.373)
2015 BOOL WINAPI LocalFileTimeToFileTime( const FILETIME *localft,
2021 /* convert from local to UTC. Perhaps not correct. FIXME */
2022 time_t unixtime = DOSFS_FileTimeToUnixTime( localft, &remainder );
2023 xtm = gmtime( &unixtime );
2024 DOSFS_UnixTimeToFileTime( mktime(xtm), utcft, remainder );
2029 /***********************************************************************
2030 * FileTimeToLocalFileTime (KERNEL32.112)
2032 BOOL WINAPI FileTimeToLocalFileTime( const FILETIME *utcft,
2033 LPFILETIME localft )
2036 /* convert from UTC to local. Perhaps not correct. FIXME */
2037 time_t unixtime = DOSFS_FileTimeToUnixTime( utcft, &remainder );
2039 struct tm *xtm = localtime( &unixtime );
2042 localtime = timegm(xtm);
2043 DOSFS_UnixTimeToFileTime( localtime, localft, remainder );
2046 struct tm *xtm,*gtm;
2049 xtm = localtime( &unixtime );
2050 gtm = gmtime( &unixtime );
2051 time1 = mktime(xtm);
2052 time2 = mktime(gtm);
2053 DOSFS_UnixTimeToFileTime( 2*time1-time2, localft, remainder );
2059 /***********************************************************************
2060 * FileTimeToSystemTime (KERNEL32.113)
2062 BOOL WINAPI FileTimeToSystemTime( const FILETIME *ft, LPSYSTEMTIME syst )
2066 time_t xtime = DOSFS_FileTimeToUnixTime( ft, &remainder );
2067 xtm = gmtime(&xtime);
2068 syst->wYear = xtm->tm_year+1900;
2069 syst->wMonth = xtm->tm_mon + 1;
2070 syst->wDayOfWeek = xtm->tm_wday;
2071 syst->wDay = xtm->tm_mday;
2072 syst->wHour = xtm->tm_hour;
2073 syst->wMinute = xtm->tm_min;
2074 syst->wSecond = xtm->tm_sec;
2075 syst->wMilliseconds = remainder / 10000;
2079 /***********************************************************************
2080 * QueryDosDeviceA (KERNEL32.413)
2082 * returns array of strings terminated by \0, terminated by \0
2084 DWORD WINAPI QueryDosDeviceA(LPCSTR devname,LPSTR target,DWORD bufsize)
2089 TRACE("(%s,...)\n", devname ? devname : "<null>");
2091 /* return known MSDOS devices */
2092 strcpy(buffer,"CON COM1 COM2 LPT1 NUL ");
2093 while ((s=strchr(buffer,' ')))
2096 lstrcpynA(target,buffer,bufsize);
2097 return strlen(buffer);
2099 strcpy(buffer,"\\DEV\\");
2100 strcat(buffer,devname);
2101 if ((s=strchr(buffer,':'))) *s='\0';
2102 lstrcpynA(target,buffer,bufsize);
2103 return strlen(buffer);
2107 /***********************************************************************
2108 * QueryDosDeviceW (KERNEL32.414)
2110 * returns array of strings terminated by \0, terminated by \0
2112 DWORD WINAPI QueryDosDeviceW(LPCWSTR devname,LPWSTR target,DWORD bufsize)
2114 LPSTR devnameA = devname?HEAP_strdupWtoA(GetProcessHeap(),0,devname):NULL;
2115 LPSTR targetA = (LPSTR)HeapAlloc(GetProcessHeap(),0,bufsize);
2116 DWORD ret = QueryDosDeviceA(devnameA,targetA,bufsize);
2118 lstrcpynAtoW(target,targetA,bufsize);
2119 if (devnameA) HeapFree(GetProcessHeap(),0,devnameA);
2120 if (targetA) HeapFree(GetProcessHeap(),0,targetA);
2125 /***********************************************************************
2126 * SystemTimeToFileTime (KERNEL32.526)
2128 BOOL WINAPI SystemTimeToFileTime( const SYSTEMTIME *syst, LPFILETIME ft )
2134 struct tm xtm,*local_tm,*utc_tm;
2135 time_t localtim,utctime;
2138 xtm.tm_year = syst->wYear-1900;
2139 xtm.tm_mon = syst->wMonth - 1;
2140 xtm.tm_wday = syst->wDayOfWeek;
2141 xtm.tm_mday = syst->wDay;
2142 xtm.tm_hour = syst->wHour;
2143 xtm.tm_min = syst->wMinute;
2144 xtm.tm_sec = syst->wSecond; /* this is UTC */
2147 utctime = timegm(&xtm);
2148 DOSFS_UnixTimeToFileTime( utctime, ft,
2149 syst->wMilliseconds * 10000 );
2151 localtim = mktime(&xtm); /* now we've got local time */
2152 local_tm = localtime(&localtim);
2153 utc_tm = gmtime(&localtim);
2154 utctime = mktime(utc_tm);
2155 DOSFS_UnixTimeToFileTime( 2*localtim -utctime, ft,
2156 syst->wMilliseconds * 10000 );
2161 /***********************************************************************
2162 * DefineDosDeviceA (KERNEL32.182)
2164 BOOL WINAPI DefineDosDeviceA(DWORD flags,LPCSTR devname,LPCSTR targetpath) {
2165 FIXME("(0x%08lx,%s,%s),stub!\n",flags,devname,targetpath);
2166 SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
2171 --- 16 bit functions ---
2174 /*************************************************************************
2175 * FindFirstFile16 (KERNEL.413)
2177 HANDLE16 WINAPI FindFirstFile16( LPCSTR path, WIN32_FIND_DATAA *data )
2179 DOS_FULL_NAME full_name;
2181 FIND_FIRST_INFO *info;
2183 data->dwReserved0 = data->dwReserved1 = 0x0;
2184 if (!path) return 0;
2185 if (!DOSFS_GetFullName( path, FALSE, &full_name ))
2186 return INVALID_HANDLE_VALUE16;
2187 if (!(handle = GlobalAlloc16( GMEM_MOVEABLE, sizeof(FIND_FIRST_INFO) )))
2188 return INVALID_HANDLE_VALUE16;
2189 info = (FIND_FIRST_INFO *)GlobalLock16( handle );
2190 info->path = HEAP_strdupA( SystemHeap, 0, full_name.long_name );
2191 info->long_mask = strrchr( info->path, '/' );
2192 if (info->long_mask )
2193 *(info->long_mask++) = '\0';
2194 info->short_mask = NULL;
2196 if (path[0] && (path[1] == ':')) info->drive = toupper(*path) - 'A';
2197 else info->drive = DRIVE_GetCurrentDrive();
2200 info->dir = DOSFS_OpenDir( info->path );
2202 GlobalUnlock16( handle );
2203 if (!FindNextFile16( handle, data ))
2205 FindClose16( handle );
2206 SetLastError( ERROR_NO_MORE_FILES );
2207 return INVALID_HANDLE_VALUE16;
2212 /*************************************************************************
2213 * FindNextFile16 (KERNEL.414)
2215 BOOL16 WINAPI FindNextFile16( HANDLE16 handle, WIN32_FIND_DATAA *data )
2217 FIND_FIRST_INFO *info;
2219 if ((handle == INVALID_HANDLE_VALUE16) ||
2220 !(info = (FIND_FIRST_INFO *)GlobalLock16( handle )))
2222 SetLastError( ERROR_INVALID_HANDLE );
2225 GlobalUnlock16( handle );
2226 if (!info->path || !info->dir)
2228 SetLastError( ERROR_NO_MORE_FILES );
2231 if (!DOSFS_FindNextEx( info, data ))
2233 DOSFS_CloseDir( info->dir ); info->dir = NULL;
2234 HeapFree( SystemHeap, 0, info->path );
2235 info->path = info->long_mask = NULL;
2236 SetLastError( ERROR_NO_MORE_FILES );
2242 /*************************************************************************
2243 * FindClose16 (KERNEL.415)
2245 BOOL16 WINAPI FindClose16( HANDLE16 handle )
2247 FIND_FIRST_INFO *info;
2249 if ((handle == INVALID_HANDLE_VALUE16) ||
2250 !(info = (FIND_FIRST_INFO *)GlobalLock16( handle )))
2252 SetLastError( ERROR_INVALID_HANDLE );
2255 if (info->dir) DOSFS_CloseDir( info->dir );
2256 if (info->path) HeapFree( SystemHeap, 0, info->path );
2257 GlobalUnlock16( handle );
2258 GlobalFree16( handle );