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>
27 #include "wine/winbase16.h"
28 #include "wine/unicode.h"
36 #include "debugtools.h"
38 DEFAULT_DEBUG_CHANNEL(dosfs);
39 DECLARE_DEBUG_CHANNEL(file);
41 /* Define the VFAT ioctl to get both short and long file names */
42 /* FIXME: is it possible to get this to work on other systems? */
44 /* We want the real kernel dirent structure, not the libc one */
49 unsigned short d_reclen;
53 #define VFAT_IOCTL_READDIR_BOTH _IOR('r', 1, KERNEL_DIRENT [2] )
56 #undef VFAT_IOCTL_READDIR_BOTH /* just in case... */
59 /* Chars we don't want to see in DOS file names */
60 #define INVALID_DOS_CHARS "*?<>|\"+=,;[] \345"
62 static const DOS_DEVICE DOSFS_Devices[] =
63 /* name, device flags (see Int 21/AX=0x4400) */
77 { "SCSIMGR$", 0xc0c0 },
81 #define GET_DRIVE(path) \
82 (((path)[1] == ':') ? FILE_toupper((path)[0]) - 'A' : DOSFS_CurDrive)
84 /* Directory info for DOSFS_ReadDir */
88 #ifdef VFAT_IOCTL_READDIR_BOTH
91 KERNEL_DIRENT dirent[2];
95 /* Info structure for FindFirstFile handle */
109 /***********************************************************************
112 * Return 1 if Unix file 'name' is also a valid MS-DOS name
113 * (i.e. contains only valid DOS chars, lower-case only, fits in 8.3 format).
114 * File name can be terminated by '\0', '\\' or '/'.
116 static int DOSFS_ValidDOSName( const char *name, int ignore_case )
118 static const char invalid_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" INVALID_DOS_CHARS;
119 const char *p = name;
120 const char *invalid = ignore_case ? (invalid_chars + 26) : invalid_chars;
125 /* Check for "." and ".." */
128 /* All other names beginning with '.' are invalid */
129 return (IS_END_OF_NAME(*p));
131 while (!IS_END_OF_NAME(*p))
133 if (strchr( invalid, *p )) return 0; /* Invalid char */
134 if (*p == '.') break; /* Start of the extension */
135 if (++len > 8) return 0; /* Name too long */
138 if (*p != '.') return 1; /* End of name */
140 if (IS_END_OF_NAME(*p)) return 0; /* Empty extension not allowed */
142 while (!IS_END_OF_NAME(*p))
144 if (strchr( invalid, *p )) return 0; /* Invalid char */
145 if (*p == '.') return 0; /* Second extension not allowed */
146 if (++len > 3) return 0; /* Extension too long */
153 /***********************************************************************
154 * DOSFS_ToDosFCBFormat
156 * Convert a file name to DOS FCB format (8+3 chars, padded with blanks),
157 * expanding wild cards and converting to upper-case in the process.
158 * File name can be terminated by '\0', '\\' or '/'.
159 * Return FALSE if the name is not a valid DOS name.
160 * 'buffer' must be at least 12 characters long.
162 BOOL DOSFS_ToDosFCBFormat( LPCSTR name, LPSTR buffer )
164 static const char invalid_chars[] = INVALID_DOS_CHARS;
165 const char *p = name;
168 /* Check for "." and ".." */
172 strcpy( buffer, ". " );
178 return (!*p || (*p == '/') || (*p == '\\'));
181 for (i = 0; i < 8; i++)
198 if (strchr( invalid_chars, *p )) return FALSE;
199 buffer[i] = FILE_toupper(*p);
207 /* Skip all chars after wildcard up to first dot */
208 while (*p && (*p != '/') && (*p != '\\') && (*p != '.')) p++;
212 /* Check if name too long */
213 if (*p && (*p != '/') && (*p != '\\') && (*p != '.')) return FALSE;
215 if (*p == '.') p++; /* Skip dot */
217 for (i = 8; i < 11; i++)
227 return FALSE; /* Second extension not allowed */
235 if (strchr( invalid_chars, *p )) return FALSE;
236 buffer[i] = FILE_toupper(*p);
243 /* at most 3 character of the extension are processed
244 * is something behind this ?
246 while (*p == '*' || *p == ' ') p++; /* skip wildcards and spaces */
247 return IS_END_OF_NAME(*p);
251 /***********************************************************************
252 * DOSFS_ToDosDTAFormat
254 * Convert a file name from FCB to DTA format (name.ext, null-terminated)
255 * converting to upper-case in the process.
256 * File name can be terminated by '\0', '\\' or '/'.
257 * 'buffer' must be at least 13 characters long.
259 static void DOSFS_ToDosDTAFormat( LPCSTR name, LPSTR buffer )
263 memcpy( buffer, name, 8 );
264 for (p = buffer + 8; (p > buffer) && (p[-1] == ' '); p--);
266 memcpy( p, name + 8, 3 );
267 for (p += 3; p[-1] == ' '; p--);
268 if (p[-1] == '.') p--;
273 /***********************************************************************
276 * Check a DOS file name against a mask (both in FCB format).
278 static int DOSFS_MatchShort( const char *mask, const char *name )
281 for (i = 11; i > 0; i--, mask++, name++)
282 if ((*mask != '?') && (*mask != *name)) return 0;
287 /***********************************************************************
290 * Check a long file name against a mask.
292 * Tests (done in W95 DOS shell - case insensitive):
293 * *.txt test1.test.txt *
295 * *.t??????.t* test1.ta.tornado.txt *
296 * *tornado* test1.ta.tornado.txt *
297 * t*t test1.ta.tornado.txt *
299 * ?est??? test1.txt -
300 * *test1.txt* test1.txt *
301 * h?l?o*t.dat hellothisisatest.dat *
303 static int DOSFS_MatchLong( const char *mask, const char *name,
306 const char *lastjoker = NULL;
307 const char *next_to_retry = NULL;
309 if (!strcmp( mask, "*.*" )) return 1;
310 while (*name && *mask)
315 while (*mask == '*') mask++; /* Skip consecutive '*' */
317 if (!*mask) return 1; /* end of mask is all '*', so match */
319 /* skip to the next match after the joker(s) */
320 if (case_sensitive) while (*name && (*name != *mask)) name++;
321 else while (*name && (FILE_toupper(*name) != FILE_toupper(*mask))) name++;
324 next_to_retry = name;
326 else if (*mask != '?')
331 if (*mask != *name) mismatch = 1;
335 if (FILE_toupper(*mask) != FILE_toupper(*name)) mismatch = 1;
349 else /* mismatch ! */
351 if (lastjoker) /* we had an '*', so we can try unlimitedly */
355 /* this scan sequence was a mismatch, so restart
356 * 1 char after the first char we checked last time */
358 name = next_to_retry;
361 return 0; /* bad luck */
370 while ((*mask == '.') || (*mask == '*'))
371 mask++; /* Ignore trailing '.' or '*' in mask */
372 return (!*name && !*mask);
376 /***********************************************************************
379 static DOS_DIR *DOSFS_OpenDir( LPCSTR path )
381 DOS_DIR *dir = HeapAlloc( GetProcessHeap(), 0, sizeof(*dir) );
384 SetLastError( ERROR_NOT_ENOUGH_MEMORY );
388 /* Treat empty path as root directory. This simplifies path split into
389 directory and mask in several other places */
390 if (!*path) path = "/";
392 #ifdef VFAT_IOCTL_READDIR_BOTH
394 /* Check if the VFAT ioctl is supported on this directory */
396 if ((dir->fd = open( path, O_RDONLY )) != -1)
398 if (ioctl( dir->fd, VFAT_IOCTL_READDIR_BOTH, (long)dir->dirent ) == -1)
405 /* Set the file pointer back at the start of the directory */
406 lseek( dir->fd, 0, SEEK_SET );
411 #endif /* VFAT_IOCTL_READDIR_BOTH */
413 /* Now use the standard opendir/readdir interface */
415 if (!(dir->dir = opendir( path )))
417 HeapFree( GetProcessHeap(), 0, dir );
424 /***********************************************************************
427 static void DOSFS_CloseDir( DOS_DIR *dir )
429 #ifdef VFAT_IOCTL_READDIR_BOTH
430 if (dir->fd != -1) close( dir->fd );
431 #endif /* VFAT_IOCTL_READDIR_BOTH */
432 if (dir->dir) closedir( dir->dir );
433 HeapFree( GetProcessHeap(), 0, dir );
437 /***********************************************************************
440 static BOOL DOSFS_ReadDir( DOS_DIR *dir, LPCSTR *long_name,
443 struct dirent *dirent;
445 #ifdef VFAT_IOCTL_READDIR_BOTH
448 if (ioctl( dir->fd, VFAT_IOCTL_READDIR_BOTH, (long)dir->dirent ) != -1) {
449 if (!dir->dirent[0].d_reclen) return FALSE;
450 if (!DOSFS_ToDosFCBFormat( dir->dirent[0].d_name, dir->short_name ))
451 dir->short_name[0] = '\0';
452 *short_name = dir->short_name;
453 if (dir->dirent[1].d_name[0]) *long_name = dir->dirent[1].d_name;
454 else *long_name = dir->dirent[0].d_name;
458 #endif /* VFAT_IOCTL_READDIR_BOTH */
460 if (!(dirent = readdir( dir->dir ))) return FALSE;
461 *long_name = dirent->d_name;
467 /***********************************************************************
470 * Transform a Unix file name into a hashed DOS name. If the name is a valid
471 * DOS name, it is converted to upper-case; otherwise it is replaced by a
472 * hashed version that fits in 8.3 format.
473 * File name can be terminated by '\0', '\\' or '/'.
474 * 'buffer' must be at least 13 characters long.
476 static void DOSFS_Hash( LPCSTR name, LPSTR buffer, BOOL dir_format,
479 static const char invalid_chars[] = INVALID_DOS_CHARS "~.";
480 static const char hash_chars[32] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345";
487 if (dir_format) strcpy( buffer, " " );
489 if (DOSFS_ValidDOSName( name, ignore_case ))
491 /* Check for '.' and '..' */
495 if (!dir_format) buffer[1] = buffer[2] = '\0';
496 if (name[1] == '.') buffer[1] = '.';
500 /* Simply copy the name, converting to uppercase */
502 for (dst = buffer; !IS_END_OF_NAME(*name) && (*name != '.'); name++)
503 *dst++ = FILE_toupper(*name);
506 if (dir_format) dst = buffer + 8;
508 for (name++; !IS_END_OF_NAME(*name); name++)
509 *dst++ = FILE_toupper(*name);
511 if (!dir_format) *dst = '\0';
515 /* Compute the hash code of the file name */
516 /* If you know something about hash functions, feel free to */
517 /* insert a better algorithm here... */
520 for (p = name, hash = 0xbeef; !IS_END_OF_NAME(p[1]); p++)
521 hash = (hash<<3) ^ (hash>>5) ^ FILE_tolower(*p) ^ (FILE_tolower(p[1]) << 8);
522 hash = (hash<<3) ^ (hash>>5) ^ FILE_tolower(*p); /* Last character*/
526 for (p = name, hash = 0xbeef; !IS_END_OF_NAME(p[1]); p++)
527 hash = (hash << 3) ^ (hash >> 5) ^ *p ^ (p[1] << 8);
528 hash = (hash << 3) ^ (hash >> 5) ^ *p; /* Last character */
531 /* Find last dot for start of the extension */
532 for (p = name+1, ext = NULL; !IS_END_OF_NAME(*p); p++)
533 if (*p == '.') ext = p;
534 if (ext && IS_END_OF_NAME(ext[1]))
535 ext = NULL; /* Empty extension ignored */
537 /* Copy first 4 chars, replacing invalid chars with '_' */
538 for (i = 4, p = name, dst = buffer; i > 0; i--, p++)
540 if (IS_END_OF_NAME(*p) || (p == ext)) break;
541 *dst++ = strchr( invalid_chars, *p ) ? '_' : FILE_toupper(*p);
543 /* Pad to 5 chars with '~' */
544 while (i-- >= 0) *dst++ = '~';
546 /* Insert hash code converted to 3 ASCII chars */
547 *dst++ = hash_chars[(hash >> 10) & 0x1f];
548 *dst++ = hash_chars[(hash >> 5) & 0x1f];
549 *dst++ = hash_chars[hash & 0x1f];
551 /* Copy the first 3 chars of the extension (if any) */
554 if (!dir_format) *dst++ = '.';
555 for (i = 3, ext++; (i > 0) && !IS_END_OF_NAME(*ext); i--, ext++)
556 *dst++ = strchr( invalid_chars, *ext ) ? '_' : FILE_toupper(*ext);
558 if (!dir_format) *dst = '\0';
562 /***********************************************************************
565 * Find the Unix file name in a given directory that corresponds to
566 * a file name (either in Unix or DOS format).
567 * File name can be terminated by '\0', '\\' or '/'.
568 * Return TRUE if OK, FALSE if no file name matches.
570 * 'long_buf' must be at least 'long_len' characters long. If the long name
571 * turns out to be larger than that, the function returns FALSE.
572 * 'short_buf' must be at least 13 characters long.
574 BOOL DOSFS_FindUnixName( LPCSTR path, LPCSTR name, LPSTR long_buf,
575 INT long_len, LPSTR short_buf, BOOL ignore_case)
578 LPCSTR long_name, short_name;
579 char dos_name[12], tmp_buf[13];
582 const char *p = strchr( name, '/' );
583 int len = p ? (int)(p - name) : strlen(name);
584 if ((p = strchr( name, '\\' ))) len = min( (int)(p - name), len );
585 /* Ignore trailing dots and spaces */
586 while (len > 1 && (name[len-1] == '.' || name[len-1] == ' ')) len--;
587 if (long_len < len + 1) return FALSE;
589 TRACE("%s,%s\n", path, name );
591 if (!DOSFS_ToDosFCBFormat( name, dos_name )) dos_name[0] = '\0';
593 if (!(dir = DOSFS_OpenDir( path )))
595 WARN("(%s,%s): can't open dir: %s\n",
596 path, name, strerror(errno) );
600 while ((ret = DOSFS_ReadDir( dir, &long_name, &short_name )))
602 /* Check against Unix name */
603 if (len == strlen(long_name))
607 if (!strncmp( long_name, name, len )) break;
611 if (!FILE_strncasecmp( long_name, name, len )) break;
616 /* Check against hashed DOS name */
619 DOSFS_Hash( long_name, tmp_buf, TRUE, ignore_case );
620 short_name = tmp_buf;
622 if (!strcmp( dos_name, short_name )) break;
627 if (long_buf) strcpy( long_buf, long_name );
631 DOSFS_ToDosDTAFormat( short_name, short_buf );
633 DOSFS_Hash( long_name, short_buf, FALSE, ignore_case );
635 TRACE("(%s,%s) -> %s (%s)\n",
636 path, name, long_name, short_buf ? short_buf : "***");
639 WARN("'%s' not found in '%s'\n", name, path);
640 DOSFS_CloseDir( dir );
645 /***********************************************************************
648 * Check if a DOS file name represents a DOS device and return the device.
650 const DOS_DEVICE *DOSFS_GetDevice( const char *name )
655 if (!name) return NULL; /* if FILE_DupUnixHandle was used */
656 if (name[0] && (name[1] == ':')) name += 2;
657 if ((p = strrchr( name, '/' ))) name = p + 1;
658 if ((p = strrchr( name, '\\' ))) name = p + 1;
659 for (i = 0; i < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0]); i++)
661 const char *dev = DOSFS_Devices[i].name;
662 if (!FILE_strncasecmp( dev, name, strlen(dev) ))
664 p = name + strlen( dev );
665 if (!*p || (*p == '.') || (*p == ':')) return &DOSFS_Devices[i];
672 /***********************************************************************
673 * DOSFS_GetDeviceByHandle
675 const DOS_DEVICE *DOSFS_GetDeviceByHandle( HFILE hFile )
677 const DOS_DEVICE *ret = NULL;
680 struct get_file_info_request *req = server_alloc_req( sizeof(*req), 0 );
683 if (!server_call( REQ_GET_FILE_INFO ) && (req->type == FILE_TYPE_UNKNOWN))
685 if ((req->attr >= 0) &&
686 (req->attr < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0])))
687 ret = &DOSFS_Devices[req->attr];
695 /**************************************************************************
696 * DOSFS_CreateCommPort
698 static HANDLE DOSFS_CreateCommPort(LPCSTR name, DWORD access)
703 TRACE("%s %lx\n", name, access);
705 PROFILE_GetWineIniString("serialports",name,"",devname,sizeof devname);
709 TRACE("opening %s as %s\n", devname, name);
713 size_t len = strlen(devname);
714 struct create_serial_request *req = server_alloc_req( sizeof(*req), len );
716 req->access = access;
717 req->inherit = 0; /*FIXME*/
718 req->sharing = FILE_SHARE_READ|FILE_SHARE_WRITE;
719 memcpy( server_data_ptr(req), devname, len );
721 server_call( REQ_CREATE_SERIAL );
726 TRACE("return %08X\n", ret );
730 /***********************************************************************
733 * Open a DOS device. This might not map 1:1 into the UNIX device concept.
734 * Returns 0 on failure.
736 HANDLE DOSFS_OpenDevice( const char *name, DWORD access )
742 if (name[0] && (name[1] == ':')) name += 2;
743 if ((p = strrchr( name, '/' ))) name = p + 1;
744 if ((p = strrchr( name, '\\' ))) name = p + 1;
745 for (i = 0; i < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0]); i++)
747 const char *dev = DOSFS_Devices[i].name;
748 if (!FILE_strncasecmp( dev, name, strlen(dev) ))
750 p = name + strlen( dev );
751 if (!*p || (*p == '.') || (*p == ':')) {
753 if (!strcmp(DOSFS_Devices[i].name,"NUL"))
754 return FILE_CreateFile( "/dev/null", access,
755 FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,
756 OPEN_EXISTING, 0, 0, TRUE );
757 if (!strcmp(DOSFS_Devices[i].name,"CON")) {
759 switch (access & (GENERIC_READ|GENERIC_WRITE)) {
761 to_dup = GetStdHandle( STD_INPUT_HANDLE );
764 to_dup = GetStdHandle( STD_OUTPUT_HANDLE );
767 FIXME("can't open CON read/write\n");
770 if (!DuplicateHandle( GetCurrentProcess(), to_dup, GetCurrentProcess(),
771 &handle, 0, FALSE, DUPLICATE_SAME_ACCESS ))
775 if (!strcmp(DOSFS_Devices[i].name,"SCSIMGR$") ||
776 !strcmp(DOSFS_Devices[i].name,"HPSCAN"))
778 return FILE_CreateDevice( i, access, NULL );
781 if( (handle=DOSFS_CreateCommPort(DOSFS_Devices[i].name,access)) )
784 FIXME("device open %s not supported (yet)\n",DOSFS_Devices[i].name);
793 /***********************************************************************
796 * Get the drive specified by a given path name (DOS or Unix format).
798 static int DOSFS_GetPathDrive( const char **name )
801 const char *p = *name;
803 if (*p && (p[1] == ':'))
805 drive = FILE_toupper(*p) - 'A';
808 else if (*p == '/') /* Absolute Unix path? */
810 if ((drive = DRIVE_FindDriveRoot( name )) == -1)
812 MESSAGE("Warning: %s not accessible from a DOS drive\n", *name );
813 /* Assume it really was a DOS name */
814 drive = DRIVE_GetCurrentDrive();
817 else drive = DRIVE_GetCurrentDrive();
819 if (!DRIVE_IsValid(drive))
821 SetLastError( ERROR_INVALID_DRIVE );
828 /***********************************************************************
831 * Convert a file name (DOS or mixed DOS/Unix format) to a valid
832 * Unix name / short DOS name pair.
833 * Return FALSE if one of the path components does not exist. The last path
834 * component is only checked if 'check_last' is non-zero.
835 * The buffers pointed to by 'long_buf' and 'short_buf' must be
836 * at least MAX_PATHNAME_LEN long.
838 BOOL DOSFS_GetFullName( LPCSTR name, BOOL check_last, DOS_FULL_NAME *full )
842 char *p_l, *p_s, *root;
844 TRACE("%s (last=%d)\n", name, check_last );
846 if ((full->drive = DOSFS_GetPathDrive( &name )) == -1) return FALSE;
847 flags = DRIVE_GetFlags( full->drive );
849 lstrcpynA( full->long_name, DRIVE_GetRoot( full->drive ),
850 sizeof(full->long_name) );
851 if (full->long_name[1]) root = full->long_name + strlen(full->long_name);
852 else root = full->long_name; /* root directory */
854 strcpy( full->short_name, "A:\\" );
855 full->short_name[0] += full->drive;
857 if ((*name == '\\') || (*name == '/')) /* Absolute path */
859 while ((*name == '\\') || (*name == '/')) name++;
861 else /* Relative path */
863 lstrcpynA( root + 1, DRIVE_GetUnixCwd( full->drive ),
864 sizeof(full->long_name) - (root - full->long_name) - 1 );
865 if (root[1]) *root = '/';
866 lstrcpynA( full->short_name + 3, DRIVE_GetDosCwd( full->drive ),
867 sizeof(full->short_name) - 3 );
870 p_l = full->long_name[1] ? full->long_name + strlen(full->long_name)
872 p_s = full->short_name[3] ? full->short_name + strlen(full->short_name)
873 : full->short_name + 2;
876 while (*name && found)
878 /* Check for '.' and '..' */
882 if (IS_END_OF_NAME(name[1]))
885 while ((*name == '\\') || (*name == '/')) name++;
888 else if ((name[1] == '.') && IS_END_OF_NAME(name[2]))
891 while ((*name == '\\') || (*name == '/')) name++;
892 while ((p_l > root) && (*p_l != '/')) p_l--;
893 while ((p_s > full->short_name + 2) && (*p_s != '\\')) p_s--;
894 *p_l = *p_s = '\0'; /* Remove trailing separator */
899 /* Make sure buffers are large enough */
901 if ((p_s >= full->short_name + sizeof(full->short_name) - 14) ||
902 (p_l >= full->long_name + sizeof(full->long_name) - 1))
904 SetLastError( ERROR_PATH_NOT_FOUND );
908 /* Get the long and short name matching the file name */
910 if ((found = DOSFS_FindUnixName( full->long_name, name, p_l + 1,
911 sizeof(full->long_name) - (p_l - full->long_name) - 1,
912 p_s + 1, !(flags & DRIVE_CASE_SENSITIVE) )))
918 while (!IS_END_OF_NAME(*name)) name++;
920 else if (!check_last)
924 while (!IS_END_OF_NAME(*name) &&
925 (p_s < full->short_name + sizeof(full->short_name) - 1) &&
926 (p_l < full->long_name + sizeof(full->long_name) - 1))
928 *p_s++ = FILE_tolower(*name);
929 /* If the drive is case-sensitive we want to create new */
930 /* files in lower-case otherwise we can't reopen them */
931 /* under the same short name. */
932 if (flags & DRIVE_CASE_SENSITIVE) *p_l++ = FILE_tolower(*name);
936 /* Ignore trailing dots and spaces */
937 while(p_l[-1] == '.' || p_l[-1] == ' ') {
943 while ((*name == '\\') || (*name == '/')) name++;
950 SetLastError( ERROR_FILE_NOT_FOUND );
953 if (*name) /* Not last */
955 SetLastError( ERROR_PATH_NOT_FOUND );
959 if (!full->long_name[0]) strcpy( full->long_name, "/" );
960 if (!full->short_name[2]) strcpy( full->short_name + 2, "\\" );
961 TRACE("returning %s = %s\n", full->long_name, full->short_name );
966 /***********************************************************************
967 * GetShortPathNameA (KERNEL32.271)
971 * longpath=NULL: LastError=ERROR_INVALID_PARAMETER, ret=0
972 * *longpath="" or invalid: LastError=ERROR_BAD_PATHNAME, ret=0
974 * more observations ( with NT 3.51 (WinDD) ):
975 * longpath <= 8.3 -> just copy longpath to shortpath
977 * a) file does not exist -> return 0, LastError = ERROR_FILE_NOT_FOUND
978 * b) file does exist -> set the short filename.
979 * - trailing slashes are reproduced in the short name, even if the
980 * file is not a directory
981 * - the absolute/relative path of the short name is reproduced like found
983 * - longpath and shortpath may have the same adress
986 DWORD WINAPI GetShortPathNameA( LPCSTR longpath, LPSTR shortpath,
989 DOS_FULL_NAME full_name;
991 DWORD sp = 0, lp = 0;
995 TRACE("%s\n", debugstr_a(longpath));
998 SetLastError(ERROR_INVALID_PARAMETER);
1002 SetLastError(ERROR_BAD_PATHNAME);
1006 if ( ( tmpshortpath = HeapAlloc ( GetProcessHeap(), 0, MAX_PATHNAME_LEN ) ) == NULL ) {
1007 SetLastError ( ERROR_NOT_ENOUGH_MEMORY );
1011 /* check for drive letter */
1012 if ( longpath[1] == ':' ) {
1013 tmpshortpath[0] = longpath[0];
1014 tmpshortpath[1] = ':';
1018 if ( ( drive = DOSFS_GetPathDrive ( &longpath )) == -1 ) return 0;
1019 flags = DRIVE_GetFlags ( drive );
1021 while ( longpath[lp] ) {
1023 /* check for path delimiters and reproduce them */
1024 if ( longpath[lp] == '\\' || longpath[lp] == '/' ) {
1025 if (!sp || tmpshortpath[sp-1]!= '\\')
1027 /* strip double "\\" */
1028 tmpshortpath[sp] = '\\';
1031 tmpshortpath[sp]=0;/*terminate string*/
1036 tmplen = strcspn ( longpath + lp, "\\/" );
1037 lstrcpynA ( tmpshortpath+sp, longpath + lp, tmplen+1 );
1039 /* Check, if the current element is a valid dos name */
1040 if ( DOSFS_ValidDOSName ( longpath + lp, !(flags & DRIVE_CASE_SENSITIVE) ) ) {
1046 /* Check if the file exists and use the existing file name */
1047 if ( DOSFS_GetFullName ( tmpshortpath, TRUE, &full_name ) ) {
1048 strcpy( tmpshortpath+sp, strrchr ( full_name.short_name, '\\' ) + 1 );
1049 sp += strlen ( tmpshortpath+sp );
1054 TRACE("not found!\n" );
1055 SetLastError ( ERROR_FILE_NOT_FOUND );
1058 tmpshortpath[sp] = 0;
1060 lstrcpynA ( shortpath, tmpshortpath, shortlen );
1061 TRACE("returning %s\n", debugstr_a(shortpath) );
1062 tmplen = strlen ( tmpshortpath );
1063 HeapFree ( GetProcessHeap(), 0, tmpshortpath );
1069 /***********************************************************************
1070 * GetShortPathNameW (KERNEL32.272)
1072 DWORD WINAPI GetShortPathNameW( LPCWSTR longpath, LPWSTR shortpath,
1075 LPSTR longpathA, shortpathA;
1078 longpathA = HEAP_strdupWtoA( GetProcessHeap(), 0, longpath );
1079 shortpathA = HeapAlloc ( GetProcessHeap(), 0, shortlen );
1081 ret = GetShortPathNameA ( longpathA, shortpathA, shortlen );
1082 if (shortlen > 0 && !MultiByteToWideChar( CP_ACP, 0, shortpathA, -1, shortpath, shortlen ))
1083 shortpath[shortlen-1] = 0;
1084 HeapFree( GetProcessHeap(), 0, longpathA );
1085 HeapFree( GetProcessHeap(), 0, shortpathA );
1091 /***********************************************************************
1092 * GetLongPathNameA (KERNEL32.xxx)
1094 DWORD WINAPI GetLongPathNameA( LPCSTR shortpath, LPSTR longpath,
1097 DOS_FULL_NAME full_name;
1098 char *p, *r, *ll, *ss;
1100 if (!DOSFS_GetFullName( shortpath, TRUE, &full_name )) return 0;
1101 lstrcpynA( longpath, full_name.short_name, longlen );
1103 /* Do some hackery to get the long filename. */
1106 ss=longpath+strlen(longpath);
1107 ll=full_name.long_name+strlen(full_name.long_name);
1109 while (ss>=longpath)
1111 /* FIXME: aren't we more paranoid, than needed? */
1112 while ((ss[0]=='\\') && (ss>=longpath)) ss--;
1114 while ((ss[0]!='\\') && (ss>=longpath)) ss--;
1117 /* FIXME: aren't we more paranoid, than needed? */
1118 while ((ll[0]=='/') && (ll>=full_name.long_name)) ll--;
1119 while ((ll[0]!='/') && (ll>=full_name.long_name)) ll--;
1120 if (ll<full_name.long_name)
1122 ERR("Bad longname! (ss=%s ll=%s)\n This should never happen !\n"
1129 /* FIXME: fix for names like "C:\\" (ie. with more '\'s) */
1133 if ((p-longpath)>0) longlen -= (p-longpath);
1134 lstrcpynA( p, ll , longlen);
1136 /* Now, change all '/' to '\' */
1137 for (r=p; r<(p+longlen); r++ )
1138 if (r[0]=='/') r[0]='\\';
1139 return strlen(longpath) - strlen(p) + longlen;
1143 return strlen(longpath);
1147 /***********************************************************************
1148 * GetLongPathNameW (KERNEL32.269)
1150 DWORD WINAPI GetLongPathNameW( LPCWSTR shortpath, LPWSTR longpath,
1153 DOS_FULL_NAME full_name;
1155 LPSTR shortpathA = HEAP_strdupWtoA( GetProcessHeap(), 0, shortpath );
1157 /* FIXME: is it correct to always return a fully qualified short path? */
1158 if (DOSFS_GetFullName( shortpathA, TRUE, &full_name ))
1160 ret = strlen( full_name.short_name );
1161 if (longlen > 0 && !MultiByteToWideChar( CP_ACP, 0, full_name.long_name, -1,
1162 longpath, longlen ))
1163 longpath[longlen-1] = 0;
1165 HeapFree( GetProcessHeap(), 0, shortpathA );
1170 /***********************************************************************
1171 * DOSFS_DoGetFullPathName
1173 * Implementation of GetFullPathNameA/W.
1175 * bon@elektron 000331:
1176 * A test for GetFullPathName with many pathological cases
1177 * now gives identical output for Wine and OSR2
1179 static DWORD DOSFS_DoGetFullPathName( LPCSTR name, DWORD len, LPSTR result,
1183 DOS_FULL_NAME full_name;
1186 char drivecur[]="c:.";
1188 int namelen,drive=0;
1190 if ((strlen(name) >1)&& (name[1]==':'))
1191 /*drive letter given */
1193 driveletter = name[0];
1195 if ((strlen(name) >2)&& (name[1]==':') &&
1196 ((name[2]=='\\') || (name[2]=='/')))
1197 /*absolute path given */
1199 lstrcpynA(full_name.short_name,name,MAX_PATHNAME_LEN);
1200 drive = (int)FILE_toupper(name[0]) - 'A';
1205 drivecur[0]=driveletter;
1207 strcpy(drivecur,".");
1208 if (!DOSFS_GetFullName( drivecur, FALSE, &full_name ))
1210 FIXME("internal: error getting drive/path\n");
1213 /* find path that drive letter substitutes*/
1214 drive = (int)FILE_toupper(full_name.short_name[0]) -0x41;
1215 root= DRIVE_GetRoot(drive);
1218 FIXME("internal: error getting DOS Drive Root\n");
1221 if (!strcmp(root,"/"))
1223 /* we have just the last / and we need it. */
1224 p= full_name.long_name;
1228 p= full_name.long_name +strlen(root);
1230 /* append long name (= unix name) to drive */
1231 lstrcpynA(full_name.short_name+2,p,MAX_PATHNAME_LEN-3);
1232 /* append name to treat */
1233 namelen= strlen(full_name.short_name);
1236 p += +2; /* skip drive name when appending */
1237 if (namelen +2 + strlen(p) > MAX_PATHNAME_LEN)
1239 FIXME("internal error: buffer too small\n");
1242 full_name.short_name[namelen++] ='\\';
1243 full_name.short_name[namelen] = 0;
1244 lstrcpynA(full_name.short_name +namelen,p,MAX_PATHNAME_LEN-namelen);
1246 /* reverse all slashes */
1247 for (p=full_name.short_name;
1248 p < full_name.short_name+strlen(full_name.short_name);
1254 /* Use memmove, as areas overlap*/
1256 while ((p = strstr(full_name.short_name,"\\..\\")))
1258 if (p > full_name.short_name+2)
1261 q = strrchr(full_name.short_name,'\\');
1262 memmove(q+1,p+4,strlen(p+4)+1);
1266 memmove(full_name.short_name+3,p+4,strlen(p+4)+1);
1269 if ((full_name.short_name[2]=='.')&&(full_name.short_name[3]=='.'))
1271 /* This case istn't treated yet : c:..\test */
1272 memmove(full_name.short_name+2,full_name.short_name+4,
1273 strlen(full_name.short_name+4)+1);
1276 while ((p = strstr(full_name.short_name,"\\.\\")))
1279 memmove(p+1,p+3,strlen(p+3)+1);
1281 if (!(DRIVE_GetFlags(drive) & DRIVE_CASE_PRESERVING))
1282 for (p = full_name.short_name; *p; p++) *p = FILE_toupper(*p);
1283 namelen=strlen(full_name.short_name);
1284 if (!strcmp(full_name.short_name+namelen-3,"\\.."))
1286 /* one more starnge case: "c:\test\test1\.."
1288 *(full_name.short_name+namelen-3)=0;
1289 q = strrchr(full_name.short_name,'\\');
1292 if (full_name.short_name[namelen-1]=='.')
1293 full_name.short_name[(namelen--)-1] =0;
1295 if (full_name.short_name[namelen-1]=='\\')
1296 full_name.short_name[(namelen--)-1] =0;
1297 TRACE("got %s\n",full_name.short_name);
1299 /* If the lpBuffer buffer is too small, the return value is the
1300 size of the buffer, in characters, required to hold the path
1301 plus the terminating \0 (tested against win95osr, bon 001118)
1303 ret = strlen(full_name.short_name);
1306 /* don't touch anything when the buffer is not large enough */
1307 SetLastError( ERROR_INSUFFICIENT_BUFFER );
1313 MultiByteToWideChar( CP_ACP, 0, full_name.short_name, -1, (LPWSTR)result, len );
1315 lstrcpynA( result, full_name.short_name, len );
1318 TRACE("returning '%s'\n", full_name.short_name );
1323 /***********************************************************************
1324 * GetFullPathNameA (KERNEL32.272)
1326 * if the path closed with '\', *lastpart is 0
1328 DWORD WINAPI GetFullPathNameA( LPCSTR name, DWORD len, LPSTR buffer,
1331 DWORD ret = DOSFS_DoGetFullPathName( name, len, buffer, FALSE );
1332 if (ret && (ret<=len) && buffer && lastpart)
1334 LPSTR p = buffer + strlen(buffer);
1338 while ((p > buffer + 2) && (*p != '\\')) p--;
1341 else *lastpart = NULL;
1347 /***********************************************************************
1348 * GetFullPathNameW (KERNEL32.273)
1350 DWORD WINAPI GetFullPathNameW( LPCWSTR name, DWORD len, LPWSTR buffer,
1353 LPSTR nameA = HEAP_strdupWtoA( GetProcessHeap(), 0, name );
1354 DWORD ret = DOSFS_DoGetFullPathName( nameA, len, (LPSTR)buffer, TRUE );
1355 HeapFree( GetProcessHeap(), 0, nameA );
1356 if (ret && (ret<=len) && buffer && lastpart)
1358 LPWSTR p = buffer + strlenW(buffer);
1359 if (*p != (WCHAR)'\\')
1361 while ((p > buffer + 2) && (*p != (WCHAR)'\\')) p--;
1364 else *lastpart = NULL;
1370 /***********************************************************************
1371 * wine_get_unix_file_name (Not a Windows API, but exported from KERNEL32)
1373 * Return the full Unix file name for a given path.
1375 BOOL WINAPI wine_get_unix_file_name( LPCSTR dos, LPSTR buffer, DWORD len )
1379 if ((ret = DOSFS_GetFullName( dos, FALSE, &path ))) lstrcpynA( buffer, dos, len );
1384 /***********************************************************************
1387 static int DOSFS_FindNextEx( FIND_FIRST_INFO *info, WIN32_FIND_DATAA *entry )
1389 DWORD attr = info->attr | FA_UNUSED | FA_ARCHIVE | FA_RDONLY | FILE_ATTRIBUTE_SYMLINK;
1390 UINT flags = DRIVE_GetFlags( info->drive );
1391 char *p, buffer[MAX_PATHNAME_LEN];
1392 const char *drive_path;
1394 LPCSTR long_name, short_name;
1395 BY_HANDLE_FILE_INFORMATION fileinfo;
1398 if ((info->attr & ~(FA_UNUSED | FA_ARCHIVE | FA_RDONLY)) == FA_LABEL)
1400 if (info->cur_pos) return 0;
1401 entry->dwFileAttributes = FILE_ATTRIBUTE_LABEL;
1402 RtlSecondsSince1970ToTime( (time_t)0, &entry->ftCreationTime );
1403 RtlSecondsSince1970ToTime( (time_t)0, &entry->ftLastAccessTime );
1404 RtlSecondsSince1970ToTime( (time_t)0, &entry->ftLastWriteTime );
1405 entry->nFileSizeHigh = 0;
1406 entry->nFileSizeLow = 0;
1407 entry->dwReserved0 = 0;
1408 entry->dwReserved1 = 0;
1409 DOSFS_ToDosDTAFormat( DRIVE_GetLabel( info->drive ), entry->cFileName );
1410 strcpy( entry->cAlternateFileName, entry->cFileName );
1412 TRACE("returning %s (%s) as label\n",
1413 entry->cFileName, entry->cAlternateFileName);
1417 drive_path = info->path + strlen(DRIVE_GetRoot( info->drive ));
1418 while ((*drive_path == '/') || (*drive_path == '\\')) drive_path++;
1419 drive_root = !*drive_path;
1421 lstrcpynA( buffer, info->path, sizeof(buffer) - 1 );
1422 strcat( buffer, "/" );
1423 p = buffer + strlen(buffer);
1425 while (DOSFS_ReadDir( info->dir, &long_name, &short_name ))
1429 /* Don't return '.' and '..' in the root of the drive */
1430 if (drive_root && (long_name[0] == '.') &&
1431 (!long_name[1] || ((long_name[1] == '.') && !long_name[2])))
1434 /* Check the long mask */
1436 if (info->long_mask)
1438 if (!DOSFS_MatchLong( info->long_mask, long_name,
1439 flags & DRIVE_CASE_SENSITIVE )) continue;
1442 /* Check the short mask */
1444 if (info->short_mask)
1448 DOSFS_Hash( long_name, dos_name, TRUE,
1449 !(flags & DRIVE_CASE_SENSITIVE) );
1450 short_name = dos_name;
1452 if (!DOSFS_MatchShort( info->short_mask, short_name )) continue;
1455 /* Check the file attributes */
1457 lstrcpynA( p, long_name, sizeof(buffer) - (int)(p - buffer) );
1458 if (!FILE_Stat( buffer, &fileinfo ))
1460 WARN("can't stat %s\n", buffer);
1463 if ((fileinfo.dwFileAttributes & FILE_ATTRIBUTE_SYMLINK) &&
1464 (fileinfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
1466 static int show_dir_symlinks = -1;
1467 if (show_dir_symlinks == -1)
1468 show_dir_symlinks = PROFILE_GetWineIniBool("wine", "ShowDirSymlinks", 0);
1469 if (!show_dir_symlinks) continue;
1472 if (fileinfo.dwFileAttributes & ~attr) continue;
1474 /* We now have a matching entry; fill the result and return */
1476 entry->dwFileAttributes = fileinfo.dwFileAttributes;
1477 entry->ftCreationTime = fileinfo.ftCreationTime;
1478 entry->ftLastAccessTime = fileinfo.ftLastAccessTime;
1479 entry->ftLastWriteTime = fileinfo.ftLastWriteTime;
1480 entry->nFileSizeHigh = fileinfo.nFileSizeHigh;
1481 entry->nFileSizeLow = fileinfo.nFileSizeLow;
1484 DOSFS_ToDosDTAFormat( short_name, entry->cAlternateFileName );
1486 DOSFS_Hash( long_name, entry->cAlternateFileName, FALSE,
1487 !(flags & DRIVE_CASE_SENSITIVE) );
1489 lstrcpynA( entry->cFileName, long_name, sizeof(entry->cFileName) );
1490 if (!(flags & DRIVE_CASE_PRESERVING)) _strlwr( entry->cFileName );
1491 TRACE("returning %s (%s) %02lx %ld\n",
1492 entry->cFileName, entry->cAlternateFileName,
1493 entry->dwFileAttributes, entry->nFileSizeLow );
1496 return 0; /* End of directory */
1499 /***********************************************************************
1502 * Find the next matching file. Return the number of entries read to find
1503 * the matching one, or 0 if no more entries.
1504 * 'short_mask' is the 8.3 mask (in FCB format), 'long_mask' is the long
1505 * file name mask. Either or both can be NULL.
1507 * NOTE: This is supposed to be only called by the int21 emulation
1508 * routines. Thus, we should own the Win16Mutex anyway.
1509 * Nevertheless, we explicitly enter it to ensure the static
1510 * directory cache is protected.
1512 int DOSFS_FindNext( const char *path, const char *short_mask,
1513 const char *long_mask, int drive, BYTE attr,
1514 int skip, WIN32_FIND_DATAA *entry )
1516 static FIND_FIRST_INFO info;
1517 LPCSTR short_name, long_name;
1522 /* Check the cached directory */
1523 if (!(info.dir && info.path == path && info.short_mask == short_mask
1524 && info.long_mask == long_mask && info.drive == drive
1525 && info.attr == attr && info.cur_pos <= skip))
1527 /* Not in the cache, open it anew */
1528 if (info.dir) DOSFS_CloseDir( info.dir );
1530 info.path = (LPSTR)path;
1531 info.long_mask = (LPSTR)long_mask;
1532 info.short_mask = (LPSTR)short_mask;
1536 info.dir = DOSFS_OpenDir( info.path );
1539 /* Skip to desired position */
1540 while (info.cur_pos < skip)
1541 if (info.dir && DOSFS_ReadDir( info.dir, &long_name, &short_name ))
1546 if (info.dir && info.cur_pos == skip && DOSFS_FindNextEx( &info, entry ))
1547 count = info.cur_pos - skip;
1553 if (info.dir) DOSFS_CloseDir( info.dir );
1554 memset( &info, '\0', sizeof(info) );
1562 /*************************************************************************
1563 * FindFirstFileExA (KERNEL32)
1565 HANDLE WINAPI FindFirstFileExA(
1567 FINDEX_INFO_LEVELS fInfoLevelId,
1568 LPVOID lpFindFileData,
1569 FINDEX_SEARCH_OPS fSearchOp,
1570 LPVOID lpSearchFilter,
1571 DWORD dwAdditionalFlags)
1573 DOS_FULL_NAME full_name;
1575 FIND_FIRST_INFO *info;
1577 if ((fSearchOp != FindExSearchNameMatch) || (dwAdditionalFlags != 0))
1579 FIXME("options not implemented 0x%08x 0x%08lx\n", fSearchOp, dwAdditionalFlags );
1580 return INVALID_HANDLE_VALUE;
1583 switch(fInfoLevelId)
1585 case FindExInfoStandard:
1587 WIN32_FIND_DATAA * data = (WIN32_FIND_DATAA *) lpFindFileData;
1588 data->dwReserved0 = data->dwReserved1 = 0x0;
1589 if (!lpFileName) return 0;
1590 if (!DOSFS_GetFullName( lpFileName, FALSE, &full_name )) break;
1591 if (!(handle = GlobalAlloc(GMEM_MOVEABLE, sizeof(FIND_FIRST_INFO)))) break;
1592 info = (FIND_FIRST_INFO *)GlobalLock( handle );
1593 info->path = HEAP_strdupA( GetProcessHeap(), 0, full_name.long_name );
1594 info->long_mask = strrchr( info->path, '/' );
1595 *(info->long_mask++) = '\0';
1596 info->short_mask = NULL;
1598 if (lpFileName[0] && (lpFileName[1] == ':'))
1599 info->drive = FILE_toupper(*lpFileName) - 'A';
1600 else info->drive = DRIVE_GetCurrentDrive();
1603 info->dir = DOSFS_OpenDir( info->path );
1605 GlobalUnlock( handle );
1606 if (!FindNextFileA( handle, data ))
1608 FindClose( handle );
1609 SetLastError( ERROR_NO_MORE_FILES );
1616 FIXME("fInfoLevelId 0x%08x not implemented\n", fInfoLevelId );
1618 return INVALID_HANDLE_VALUE;
1621 /*************************************************************************
1622 * FindFirstFileA (KERNEL32.123)
1624 HANDLE WINAPI FindFirstFileA(
1626 WIN32_FIND_DATAA *lpFindData )
1628 return FindFirstFileExA(lpFileName, FindExInfoStandard, lpFindData,
1629 FindExSearchNameMatch, NULL, 0);
1632 /*************************************************************************
1633 * FindFirstFileExW (KERNEL32)
1635 HANDLE WINAPI FindFirstFileExW(
1637 FINDEX_INFO_LEVELS fInfoLevelId,
1638 LPVOID lpFindFileData,
1639 FINDEX_SEARCH_OPS fSearchOp,
1640 LPVOID lpSearchFilter,
1641 DWORD dwAdditionalFlags)
1644 WIN32_FIND_DATAA dataA;
1645 LPVOID _lpFindFileData;
1648 switch(fInfoLevelId)
1650 case FindExInfoStandard:
1652 _lpFindFileData = &dataA;
1656 FIXME("fInfoLevelId 0x%08x not implemented\n", fInfoLevelId );
1657 return INVALID_HANDLE_VALUE;
1660 pathA = HEAP_strdupWtoA( GetProcessHeap(), 0, lpFileName );
1661 handle = FindFirstFileExA(pathA, fInfoLevelId, _lpFindFileData, fSearchOp, lpSearchFilter, dwAdditionalFlags);
1662 HeapFree( GetProcessHeap(), 0, pathA );
1663 if (handle == INVALID_HANDLE_VALUE) return handle;
1665 switch(fInfoLevelId)
1667 case FindExInfoStandard:
1669 WIN32_FIND_DATAW *dataW = (WIN32_FIND_DATAW*) lpFindFileData;
1670 dataW->dwFileAttributes = dataA.dwFileAttributes;
1671 dataW->ftCreationTime = dataA.ftCreationTime;
1672 dataW->ftLastAccessTime = dataA.ftLastAccessTime;
1673 dataW->ftLastWriteTime = dataA.ftLastWriteTime;
1674 dataW->nFileSizeHigh = dataA.nFileSizeHigh;
1675 dataW->nFileSizeLow = dataA.nFileSizeLow;
1676 MultiByteToWideChar( CP_ACP, 0, dataA.cFileName, -1,
1677 dataW->cFileName, sizeof(dataW->cFileName)/sizeof(WCHAR) );
1678 MultiByteToWideChar( CP_ACP, 0, dataA.cAlternateFileName, -1,
1679 dataW->cAlternateFileName,
1680 sizeof(dataW->cAlternateFileName)/sizeof(WCHAR) );
1684 FIXME("fInfoLevelId 0x%08x not implemented\n", fInfoLevelId );
1685 return INVALID_HANDLE_VALUE;
1690 /*************************************************************************
1691 * FindFirstFileW (KERNEL32.124)
1693 HANDLE WINAPI FindFirstFileW( LPCWSTR lpFileName, WIN32_FIND_DATAW *lpFindData )
1695 return FindFirstFileExW(lpFileName, FindExInfoStandard, lpFindData,
1696 FindExSearchNameMatch, NULL, 0);
1699 /*************************************************************************
1700 * FindNextFileA (KERNEL32.126)
1702 BOOL WINAPI FindNextFileA( HANDLE handle, WIN32_FIND_DATAA *data )
1704 FIND_FIRST_INFO *info;
1706 if ((handle == INVALID_HANDLE_VALUE) ||
1707 !(info = (FIND_FIRST_INFO *)GlobalLock( handle )))
1709 SetLastError( ERROR_INVALID_HANDLE );
1712 GlobalUnlock( handle );
1713 if (!info->path || !info->dir)
1715 SetLastError( ERROR_NO_MORE_FILES );
1718 if (!DOSFS_FindNextEx( info, data ))
1720 DOSFS_CloseDir( info->dir ); info->dir = NULL;
1721 HeapFree( GetProcessHeap(), 0, info->path );
1722 info->path = info->long_mask = NULL;
1723 SetLastError( ERROR_NO_MORE_FILES );
1730 /*************************************************************************
1731 * FindNextFileW (KERNEL32.127)
1733 BOOL WINAPI FindNextFileW( HANDLE handle, WIN32_FIND_DATAW *data )
1735 WIN32_FIND_DATAA dataA;
1736 if (!FindNextFileA( handle, &dataA )) return FALSE;
1737 data->dwFileAttributes = dataA.dwFileAttributes;
1738 data->ftCreationTime = dataA.ftCreationTime;
1739 data->ftLastAccessTime = dataA.ftLastAccessTime;
1740 data->ftLastWriteTime = dataA.ftLastWriteTime;
1741 data->nFileSizeHigh = dataA.nFileSizeHigh;
1742 data->nFileSizeLow = dataA.nFileSizeLow;
1743 MultiByteToWideChar( CP_ACP, 0, dataA.cFileName, -1,
1744 data->cFileName, sizeof(data->cFileName)/sizeof(WCHAR) );
1745 MultiByteToWideChar( CP_ACP, 0, dataA.cAlternateFileName, -1,
1746 data->cAlternateFileName,
1747 sizeof(data->cAlternateFileName)/sizeof(WCHAR) );
1751 /*************************************************************************
1752 * FindClose (KERNEL32.119)
1754 BOOL WINAPI FindClose( HANDLE handle )
1756 FIND_FIRST_INFO *info;
1758 if ((handle == INVALID_HANDLE_VALUE) ||
1759 !(info = (FIND_FIRST_INFO *)GlobalLock( handle )))
1761 SetLastError( ERROR_INVALID_HANDLE );
1764 if (info->dir) DOSFS_CloseDir( info->dir );
1765 if (info->path) HeapFree( GetProcessHeap(), 0, info->path );
1766 GlobalUnlock( handle );
1767 GlobalFree( handle );
1771 /***********************************************************************
1772 * DOSFS_UnixTimeToFileTime
1774 * Convert a Unix time to FILETIME format.
1775 * The FILETIME structure is a 64-bit value representing the number of
1776 * 100-nanosecond intervals since January 1, 1601, 0:00.
1777 * 'remainder' is the nonnegative number of 100-ns intervals
1778 * corresponding to the time fraction smaller than 1 second that
1779 * couldn't be stored in the time_t value.
1781 void DOSFS_UnixTimeToFileTime( time_t unix_time, FILETIME *filetime,
1787 The time difference between 1 January 1601, 00:00:00 and
1788 1 January 1970, 00:00:00 is 369 years, plus the leap years
1789 from 1604 to 1968, excluding 1700, 1800, 1900.
1790 This makes (1968 - 1600) / 4 - 3 = 89 leap days, and a total
1793 Any day in that period had 24 * 60 * 60 = 86400 seconds.
1795 The time difference is 134774 * 86400 * 10000000, which can be written
1797 27111902 * 2^32 + 3577643008
1798 413 * 2^48 + 45534 * 2^32 + 54590 * 2^16 + 32768
1800 If you find that these constants are buggy, please change them in all
1801 instances in both conversion functions.
1804 There are two versions, one of them uses long long variables and
1805 is presumably faster but not ISO C. The other one uses standard C
1806 data types and operations but relies on the assumption that negative
1807 numbers are stored as 2's complement (-1 is 0xffff....). If this
1808 assumption is violated, dates before 1970 will not convert correctly.
1809 This should however work on any reasonable architecture where WINE
1814 Take care not to remove the casts. I have tested these functions
1815 (in both versions) for a lot of numbers. I would be interested in
1816 results on other compilers than GCC.
1818 The operations have been designed to account for the possibility
1819 of 64-bit time_t in future UNICES. Even the versions without
1820 internal long long numbers will work if time_t only is 64 bit.
1821 A 32-bit shift, which was necessary for that operation, turned out
1822 not to work correctly in GCC, besides giving the warning. So I
1823 used a double 16-bit shift instead. Numbers are in the ISO version
1824 represented by three limbs, the most significant with 32 bit, the
1825 other two with 16 bit each.
1827 As the modulo-operator % is not well-defined for negative numbers,
1828 negative divisors have been avoided in DOSFS_FileTimeToUnixTime.
1830 There might be quicker ways to do this in C. Certainly so in
1833 Claus Fischer, fischer@iue.tuwien.ac.at
1836 #if SIZEOF_LONG_LONG >= 8
1837 # define USE_LONG_LONG 1
1839 # define USE_LONG_LONG 0
1842 #if USE_LONG_LONG /* gcc supports long long type */
1844 long long int t = unix_time;
1846 t += 116444736000000000LL;
1848 filetime->dwLowDateTime = (UINT)t;
1849 filetime->dwHighDateTime = (UINT)(t >> 32);
1851 #else /* ISO version */
1853 UINT a0; /* 16 bit, low bits */
1854 UINT a1; /* 16 bit, medium bits */
1855 UINT a2; /* 32 bit, high bits */
1857 /* Copy the unix time to a2/a1/a0 */
1858 a0 = unix_time & 0xffff;
1859 a1 = (unix_time >> 16) & 0xffff;
1860 /* This is obsolete if unix_time is only 32 bits, but it does not hurt.
1861 Do not replace this by >> 32, it gives a compiler warning and it does
1863 a2 = (unix_time >= 0 ? (unix_time >> 16) >> 16 :
1864 ~((~unix_time >> 16) >> 16));
1866 /* Multiply a by 10000000 (a = a2/a1/a0)
1867 Split the factor into 10000 * 1000 which are both less than 0xffff. */
1869 a1 = a1 * 10000 + (a0 >> 16);
1870 a2 = a2 * 10000 + (a1 >> 16);
1875 a1 = a1 * 1000 + (a0 >> 16);
1876 a2 = a2 * 1000 + (a1 >> 16);
1880 /* Add the time difference and the remainder */
1881 a0 += 32768 + (remainder & 0xffff);
1882 a1 += 54590 + (remainder >> 16 ) + (a0 >> 16);
1883 a2 += 27111902 + (a1 >> 16);
1888 filetime->dwLowDateTime = (a1 << 16) + a0;
1889 filetime->dwHighDateTime = a2;
1894 /***********************************************************************
1895 * DOSFS_FileTimeToUnixTime
1897 * Convert a FILETIME format to Unix time.
1898 * If not NULL, 'remainder' contains the fractional part of the filetime,
1899 * in the range of [0..9999999] (even if time_t is negative).
1901 time_t DOSFS_FileTimeToUnixTime( const FILETIME *filetime, DWORD *remainder )
1903 /* Read the comment in the function DOSFS_UnixTimeToFileTime. */
1906 long long int t = filetime->dwHighDateTime;
1908 t += (UINT)filetime->dwLowDateTime;
1909 t -= 116444736000000000LL;
1912 if (remainder) *remainder = 9999999 - (-t - 1) % 10000000;
1913 return -1 - ((-t - 1) / 10000000);
1917 if (remainder) *remainder = t % 10000000;
1918 return t / 10000000;
1921 #else /* ISO version */
1923 UINT a0; /* 16 bit, low bits */
1924 UINT a1; /* 16 bit, medium bits */
1925 UINT a2; /* 32 bit, high bits */
1926 UINT r; /* remainder of division */
1927 unsigned int carry; /* carry bit for subtraction */
1928 int negative; /* whether a represents a negative value */
1930 /* Copy the time values to a2/a1/a0 */
1931 a2 = (UINT)filetime->dwHighDateTime;
1932 a1 = ((UINT)filetime->dwLowDateTime ) >> 16;
1933 a0 = ((UINT)filetime->dwLowDateTime ) & 0xffff;
1935 /* Subtract the time difference */
1936 if (a0 >= 32768 ) a0 -= 32768 , carry = 0;
1937 else a0 += (1 << 16) - 32768 , carry = 1;
1939 if (a1 >= 54590 + carry) a1 -= 54590 + carry, carry = 0;
1940 else a1 += (1 << 16) - 54590 - carry, carry = 1;
1942 a2 -= 27111902 + carry;
1944 /* If a is negative, replace a by (-1-a) */
1945 negative = (a2 >= ((UINT)1) << 31);
1948 /* Set a to -a - 1 (a is a2/a1/a0) */
1954 /* Divide a by 10000000 (a = a2/a1/a0), put the rest into r.
1955 Split the divisor into 10000 * 1000 which are both less than 0xffff. */
1956 a1 += (a2 % 10000) << 16;
1958 a0 += (a1 % 10000) << 16;
1963 a1 += (a2 % 1000) << 16;
1965 a0 += (a1 % 1000) << 16;
1967 r += (a0 % 1000) * 10000;
1970 /* If a was negative, replace a by (-1-a) and r by (9999999 - r) */
1973 /* Set a to -a - 1 (a is a2/a1/a0) */
1981 if (remainder) *remainder = r;
1983 /* Do not replace this by << 32, it gives a compiler warning and it does
1985 return ((((time_t)a2) << 16) << 16) + (a1 << 16) + a0;
1990 /***********************************************************************
1991 * MulDiv (KERNEL32.391)
1993 * Result of multiplication and division
1994 * -1: Overflow occurred or Divisor was 0
2001 #if SIZEOF_LONG_LONG >= 8
2004 if (!nDivisor) return -1;
2006 /* We want to deal with a positive divisor to simplify the logic. */
2009 nMultiplicand = - nMultiplicand;
2010 nDivisor = -nDivisor;
2013 /* If the result is positive, we "add" to round. else, we subtract to round. */
2014 if ( ( (nMultiplicand < 0) && (nMultiplier < 0) ) ||
2015 ( (nMultiplicand >= 0) && (nMultiplier >= 0) ) )
2016 ret = (((long long)nMultiplicand * nMultiplier) + (nDivisor/2)) / nDivisor;
2018 ret = (((long long)nMultiplicand * nMultiplier) - (nDivisor/2)) / nDivisor;
2020 if ((ret > 2147483647) || (ret < -2147483647)) return -1;
2023 if (!nDivisor) return -1;
2025 /* We want to deal with a positive divisor to simplify the logic. */
2028 nMultiplicand = - nMultiplicand;
2029 nDivisor = -nDivisor;
2032 /* If the result is positive, we "add" to round. else, we subtract to round. */
2033 if ( ( (nMultiplicand < 0) && (nMultiplier < 0) ) ||
2034 ( (nMultiplicand >= 0) && (nMultiplier >= 0) ) )
2035 return ((nMultiplicand * nMultiplier) + (nDivisor/2)) / nDivisor;
2037 return ((nMultiplicand * nMultiplier) - (nDivisor/2)) / nDivisor;
2043 /***********************************************************************
2044 * DosDateTimeToFileTime (KERNEL32.76)
2046 BOOL WINAPI DosDateTimeToFileTime( WORD fatdate, WORD fattime, LPFILETIME ft)
2050 newtm.tm_sec = (fattime & 0x1f) * 2;
2051 newtm.tm_min = (fattime >> 5) & 0x3f;
2052 newtm.tm_hour = (fattime >> 11);
2053 newtm.tm_mday = (fatdate & 0x1f);
2054 newtm.tm_mon = ((fatdate >> 5) & 0x0f) - 1;
2055 newtm.tm_year = (fatdate >> 9) + 80;
2056 RtlSecondsSince1970ToTime( mktime( &newtm ), ft );
2061 /***********************************************************************
2062 * FileTimeToDosDateTime (KERNEL32.111)
2064 BOOL WINAPI FileTimeToDosDateTime( const FILETIME *ft, LPWORD fatdate,
2067 time_t unixtime = DOSFS_FileTimeToUnixTime( ft, NULL );
2068 struct tm *tm = localtime( &unixtime );
2070 *fattime = (tm->tm_hour << 11) + (tm->tm_min << 5) + (tm->tm_sec / 2);
2072 *fatdate = ((tm->tm_year - 80) << 9) + ((tm->tm_mon + 1) << 5)
2078 /***********************************************************************
2079 * LocalFileTimeToFileTime (KERNEL32.373)
2081 BOOL WINAPI LocalFileTimeToFileTime( const FILETIME *localft,
2087 /* convert from local to UTC. Perhaps not correct. FIXME */
2088 time_t unixtime = DOSFS_FileTimeToUnixTime( localft, &remainder );
2089 xtm = gmtime( &unixtime );
2090 DOSFS_UnixTimeToFileTime( mktime(xtm), utcft, remainder );
2095 /***********************************************************************
2096 * FileTimeToLocalFileTime (KERNEL32.112)
2098 BOOL WINAPI FileTimeToLocalFileTime( const FILETIME *utcft,
2099 LPFILETIME localft )
2102 /* convert from UTC to local. Perhaps not correct. FIXME */
2103 time_t unixtime = DOSFS_FileTimeToUnixTime( utcft, &remainder );
2105 struct tm *xtm = localtime( &unixtime );
2108 localtime = timegm(xtm);
2109 DOSFS_UnixTimeToFileTime( localtime, localft, remainder );
2112 struct tm *xtm,*gtm;
2115 xtm = localtime( &unixtime );
2116 gtm = gmtime( &unixtime );
2117 time1 = mktime(xtm);
2118 time2 = mktime(gtm);
2119 DOSFS_UnixTimeToFileTime( 2*time1-time2, localft, remainder );
2125 /***********************************************************************
2126 * FileTimeToSystemTime (KERNEL32.113)
2128 BOOL WINAPI FileTimeToSystemTime( const FILETIME *ft, LPSYSTEMTIME syst )
2132 time_t xtime = DOSFS_FileTimeToUnixTime( ft, &remainder );
2133 xtm = gmtime(&xtime);
2134 syst->wYear = xtm->tm_year+1900;
2135 syst->wMonth = xtm->tm_mon + 1;
2136 syst->wDayOfWeek = xtm->tm_wday;
2137 syst->wDay = xtm->tm_mday;
2138 syst->wHour = xtm->tm_hour;
2139 syst->wMinute = xtm->tm_min;
2140 syst->wSecond = xtm->tm_sec;
2141 syst->wMilliseconds = remainder / 10000;
2145 /***********************************************************************
2146 * QueryDosDeviceA (KERNEL32.413)
2148 * returns array of strings terminated by \0, terminated by \0
2150 DWORD WINAPI QueryDosDeviceA(LPCSTR devname,LPSTR target,DWORD bufsize)
2155 TRACE("(%s,...)\n", devname ? devname : "<null>");
2157 /* return known MSDOS devices */
2158 static const char devices[24] = "CON\0COM1\0COM2\0LPT1\0NUL\0\0";
2159 memcpy( target, devices, min(bufsize,sizeof(devices)) );
2160 return min(bufsize,sizeof(devices));
2162 strcpy(buffer,"\\DEV\\");
2163 strcat(buffer,devname);
2164 if ((s=strchr(buffer,':'))) *s='\0';
2165 lstrcpynA(target,buffer,bufsize);
2166 return strlen(buffer)+1;
2170 /***********************************************************************
2171 * QueryDosDeviceW (KERNEL32.414)
2173 * returns array of strings terminated by \0, terminated by \0
2175 DWORD WINAPI QueryDosDeviceW(LPCWSTR devname,LPWSTR target,DWORD bufsize)
2177 LPSTR devnameA = devname?HEAP_strdupWtoA(GetProcessHeap(),0,devname):NULL;
2178 LPSTR targetA = (LPSTR)HeapAlloc(GetProcessHeap(),0,bufsize);
2179 DWORD ret = QueryDosDeviceA(devnameA,targetA,bufsize);
2181 ret = MultiByteToWideChar( CP_ACP, 0, targetA, ret, target, bufsize );
2182 if (devnameA) HeapFree(GetProcessHeap(),0,devnameA);
2183 if (targetA) HeapFree(GetProcessHeap(),0,targetA);
2188 /***********************************************************************
2189 * SystemTimeToFileTime (KERNEL32.526)
2191 BOOL WINAPI SystemTimeToFileTime( const SYSTEMTIME *syst, LPFILETIME ft )
2197 struct tm xtm,*local_tm,*utc_tm;
2198 time_t localtim,utctime;
2201 xtm.tm_year = syst->wYear-1900;
2202 xtm.tm_mon = syst->wMonth - 1;
2203 xtm.tm_wday = syst->wDayOfWeek;
2204 xtm.tm_mday = syst->wDay;
2205 xtm.tm_hour = syst->wHour;
2206 xtm.tm_min = syst->wMinute;
2207 xtm.tm_sec = syst->wSecond; /* this is UTC */
2210 utctime = timegm(&xtm);
2211 DOSFS_UnixTimeToFileTime( utctime, ft,
2212 syst->wMilliseconds * 10000 );
2214 localtim = mktime(&xtm); /* now we've got local time */
2215 local_tm = localtime(&localtim);
2216 utc_tm = gmtime(&localtim);
2217 utctime = mktime(utc_tm);
2218 DOSFS_UnixTimeToFileTime( 2*localtim -utctime, ft,
2219 syst->wMilliseconds * 10000 );
2224 /***********************************************************************
2225 * DefineDosDeviceA (KERNEL32.182)
2227 BOOL WINAPI DefineDosDeviceA(DWORD flags,LPCSTR devname,LPCSTR targetpath) {
2228 FIXME("(0x%08lx,%s,%s),stub!\n",flags,devname,targetpath);
2229 SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
2234 --- 16 bit functions ---
2237 /*************************************************************************
2238 * FindFirstFile16 (KERNEL.413)
2240 HANDLE16 WINAPI FindFirstFile16( LPCSTR path, WIN32_FIND_DATAA *data )
2242 DOS_FULL_NAME full_name;
2244 FIND_FIRST_INFO *info;
2246 data->dwReserved0 = data->dwReserved1 = 0x0;
2247 if (!path) return 0;
2248 if (!DOSFS_GetFullName( path, FALSE, &full_name ))
2249 return INVALID_HANDLE_VALUE16;
2250 if (!(handle = GlobalAlloc16( GMEM_MOVEABLE, sizeof(FIND_FIRST_INFO) )))
2251 return INVALID_HANDLE_VALUE16;
2252 info = (FIND_FIRST_INFO *)GlobalLock16( handle );
2253 info->path = HEAP_strdupA( SystemHeap, 0, full_name.long_name );
2254 info->long_mask = strrchr( info->path, '/' );
2255 if (info->long_mask )
2256 *(info->long_mask++) = '\0';
2257 info->short_mask = NULL;
2259 if (path[0] && (path[1] == ':')) info->drive = FILE_toupper(*path) - 'A';
2260 else info->drive = DRIVE_GetCurrentDrive();
2263 info->dir = DOSFS_OpenDir( info->path );
2265 GlobalUnlock16( handle );
2266 if (!FindNextFile16( handle, data ))
2268 FindClose16( handle );
2269 SetLastError( ERROR_NO_MORE_FILES );
2270 return INVALID_HANDLE_VALUE16;
2275 /*************************************************************************
2276 * FindNextFile16 (KERNEL.414)
2278 BOOL16 WINAPI FindNextFile16( HANDLE16 handle, WIN32_FIND_DATAA *data )
2280 FIND_FIRST_INFO *info;
2282 if ((handle == INVALID_HANDLE_VALUE16) ||
2283 !(info = (FIND_FIRST_INFO *)GlobalLock16( handle )))
2285 SetLastError( ERROR_INVALID_HANDLE );
2288 GlobalUnlock16( handle );
2289 if (!info->path || !info->dir)
2291 SetLastError( ERROR_NO_MORE_FILES );
2294 if (!DOSFS_FindNextEx( info, data ))
2296 DOSFS_CloseDir( info->dir ); info->dir = NULL;
2297 HeapFree( SystemHeap, 0, info->path );
2298 info->path = info->long_mask = NULL;
2299 SetLastError( ERROR_NO_MORE_FILES );
2305 /*************************************************************************
2306 * FindClose16 (KERNEL.415)
2308 BOOL16 WINAPI FindClose16( HANDLE16 handle )
2310 FIND_FIRST_INFO *info;
2312 if ((handle == INVALID_HANDLE_VALUE16) ||
2313 !(info = (FIND_FIRST_INFO *)GlobalLock16( handle )))
2315 SetLastError( ERROR_INVALID_HANDLE );
2318 if (info->dir) DOSFS_CloseDir( info->dir );
2319 if (info->path) HeapFree( SystemHeap, 0, info->path );
2320 GlobalUnlock16( handle );
2321 GlobalFree16( handle );