2 * DOS file system functions
4 * Copyright 1993 Erik Bos
5 * Copyright 1996 Alexandre Julliard
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 #include <sys/types.h>
28 #ifdef HAVE_SYS_ERRNO_H
29 #include <sys/errno.h>
35 #ifdef HAVE_SYS_IOCTL_H
36 #include <sys/ioctl.h>
45 #include "wine/unicode.h"
46 #include "wine/winbase16.h"
52 #include "wine/server.h"
53 #include "msvcrt/excpt.h"
57 #include "wine/debug.h"
59 WINE_DEFAULT_DEBUG_CHANNEL(dosfs);
60 WINE_DECLARE_DEBUG_CHANNEL(file);
62 /* Define the VFAT ioctl to get both short and long file names */
63 /* FIXME: is it possible to get this to work on other systems? */
65 /* We want the real kernel dirent structure, not the libc one */
70 unsigned short d_reclen;
74 #define VFAT_IOCTL_READDIR_BOTH _IOR('r', 1, KERNEL_DIRENT [2] )
77 #undef VFAT_IOCTL_READDIR_BOTH /* just in case... */
80 /* Chars we don't want to see in DOS file names */
81 #define INVALID_DOS_CHARS "*?<>|\"+=,;[] \345"
83 static const DOS_DEVICE DOSFS_Devices[] =
84 /* name, device flags (see Int 21/AX=0x4400) */
98 { "SCSIMGR$", 0xc0c0 },
100 { "EMMXXXX0", 0x0000 }
103 #define GET_DRIVE(path) \
104 (((path)[1] == ':') ? FILE_toupper((path)[0]) - 'A' : DOSFS_CurDrive)
106 /* Directory info for DOSFS_ReadDir */
110 #ifdef VFAT_IOCTL_READDIR_BOTH
113 KERNEL_DIRENT dirent[2];
117 /* Info structure for FindFirstFile handle */
134 static WINE_EXCEPTION_FILTER(page_fault)
136 if (GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION)
137 return EXCEPTION_EXECUTE_HANDLER;
138 return EXCEPTION_CONTINUE_SEARCH;
142 /***********************************************************************
145 * Return 1 if Unix file 'name' is also a valid MS-DOS name
146 * (i.e. contains only valid DOS chars, lower-case only, fits in 8.3 format).
147 * File name can be terminated by '\0', '\\' or '/'.
149 static int DOSFS_ValidDOSName( const char *name, int ignore_case )
151 static const char invalid_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" INVALID_DOS_CHARS;
152 const char *p = name;
153 const char *invalid = ignore_case ? (invalid_chars + 26) : invalid_chars;
158 /* Check for "." and ".." */
161 /* All other names beginning with '.' are invalid */
162 return (IS_END_OF_NAME(*p));
164 while (!IS_END_OF_NAME(*p))
166 if (strchr( invalid, *p )) return 0; /* Invalid char */
167 if (*p == '.') break; /* Start of the extension */
168 if (++len > 8) return 0; /* Name too long */
171 if (*p != '.') return 1; /* End of name */
173 if (IS_END_OF_NAME(*p)) return 0; /* Empty extension not allowed */
175 while (!IS_END_OF_NAME(*p))
177 if (strchr( invalid, *p )) return 0; /* Invalid char */
178 if (*p == '.') return 0; /* Second extension not allowed */
179 if (++len > 3) return 0; /* Extension too long */
186 /***********************************************************************
187 * DOSFS_ToDosFCBFormat
189 * Convert a file name to DOS FCB format (8+3 chars, padded with blanks),
190 * expanding wild cards and converting to upper-case in the process.
191 * File name can be terminated by '\0', '\\' or '/'.
192 * Return FALSE if the name is not a valid DOS name.
193 * 'buffer' must be at least 12 characters long.
195 BOOL DOSFS_ToDosFCBFormat( LPCSTR name, LPSTR buffer )
197 static const char invalid_chars[] = INVALID_DOS_CHARS;
198 const char *p = name;
201 /* Check for "." and ".." */
205 strcpy( buffer, ". " );
211 return (!*p || (*p == '/') || (*p == '\\'));
214 for (i = 0; i < 8; i++)
231 if (strchr( invalid_chars, *p )) return FALSE;
232 buffer[i] = FILE_toupper(*p);
240 /* Skip all chars after wildcard up to first dot */
241 while (*p && (*p != '/') && (*p != '\\') && (*p != '.')) p++;
245 /* Check if name too long */
246 if (*p && (*p != '/') && (*p != '\\') && (*p != '.')) return FALSE;
248 if (*p == '.') p++; /* Skip dot */
250 for (i = 8; i < 11; i++)
260 return FALSE; /* Second extension not allowed */
268 if (strchr( invalid_chars, *p )) return FALSE;
269 buffer[i] = FILE_toupper(*p);
276 /* at most 3 character of the extension are processed
277 * is something behind this ?
279 while (*p == '*' || *p == ' ') p++; /* skip wildcards and spaces */
280 return IS_END_OF_NAME(*p);
284 /***********************************************************************
285 * DOSFS_ToDosDTAFormat
287 * Convert a file name from FCB to DTA format (name.ext, null-terminated)
288 * converting to upper-case in the process.
289 * File name can be terminated by '\0', '\\' or '/'.
290 * 'buffer' must be at least 13 characters long.
292 static void DOSFS_ToDosDTAFormat( LPCSTR name, LPSTR buffer )
296 memcpy( buffer, name, 8 );
298 while ((p > buffer) && (p[-1] == ' ')) p--;
300 memcpy( p, name + 8, 3 );
302 while (p[-1] == ' ') p--;
303 if (p[-1] == '.') p--;
308 /***********************************************************************
311 * Check a DOS file name against a mask (both in FCB format).
313 static int DOSFS_MatchShort( const char *mask, const char *name )
316 for (i = 11; i > 0; i--, mask++, name++)
317 if ((*mask != '?') && (*mask != *name)) return 0;
322 /***********************************************************************
325 * Check a long file name against a mask.
327 * Tests (done in W95 DOS shell - case insensitive):
328 * *.txt test1.test.txt *
330 * *.t??????.t* test1.ta.tornado.txt *
331 * *tornado* test1.ta.tornado.txt *
332 * t*t test1.ta.tornado.txt *
334 * ?est??? test1.txt -
335 * *test1.txt* test1.txt *
336 * h?l?o*t.dat hellothisisatest.dat *
338 static int DOSFS_MatchLong( const char *mask, const char *name,
341 const char *lastjoker = NULL;
342 const char *next_to_retry = NULL;
344 if (!strcmp( mask, "*.*" )) return 1;
345 while (*name && *mask)
350 while (*mask == '*') mask++; /* Skip consecutive '*' */
352 if (!*mask) return 1; /* end of mask is all '*', so match */
354 /* skip to the next match after the joker(s) */
355 if (case_sensitive) while (*name && (*name != *mask)) name++;
356 else while (*name && (FILE_toupper(*name) != FILE_toupper(*mask))) name++;
359 next_to_retry = name;
361 else if (*mask != '?')
366 if (*mask != *name) mismatch = 1;
370 if (FILE_toupper(*mask) != FILE_toupper(*name)) mismatch = 1;
384 else /* mismatch ! */
386 if (lastjoker) /* we had an '*', so we can try unlimitedly */
390 /* this scan sequence was a mismatch, so restart
391 * 1 char after the first char we checked last time */
393 name = next_to_retry;
396 return 0; /* bad luck */
405 while ((*mask == '.') || (*mask == '*'))
406 mask++; /* Ignore trailing '.' or '*' in mask */
407 return (!*name && !*mask);
411 /***********************************************************************
414 static DOS_DIR *DOSFS_OpenDir( LPCSTR path )
416 DOS_DIR *dir = HeapAlloc( GetProcessHeap(), 0, sizeof(*dir) );
419 SetLastError( ERROR_NOT_ENOUGH_MEMORY );
423 /* Treat empty path as root directory. This simplifies path split into
424 directory and mask in several other places */
425 if (!*path) path = "/";
427 #ifdef VFAT_IOCTL_READDIR_BOTH
429 /* Check if the VFAT ioctl is supported on this directory */
431 if ((dir->fd = open( path, O_RDONLY )) != -1)
433 if (ioctl( dir->fd, VFAT_IOCTL_READDIR_BOTH, (long)dir->dirent ) == -1)
440 /* Set the file pointer back at the start of the directory */
441 lseek( dir->fd, 0, SEEK_SET );
446 #endif /* VFAT_IOCTL_READDIR_BOTH */
448 /* Now use the standard opendir/readdir interface */
450 if (!(dir->dir = opendir( path )))
452 HeapFree( GetProcessHeap(), 0, dir );
459 /***********************************************************************
462 static void DOSFS_CloseDir( DOS_DIR *dir )
464 #ifdef VFAT_IOCTL_READDIR_BOTH
465 if (dir->fd != -1) close( dir->fd );
466 #endif /* VFAT_IOCTL_READDIR_BOTH */
467 if (dir->dir) closedir( dir->dir );
468 HeapFree( GetProcessHeap(), 0, dir );
472 /***********************************************************************
475 static BOOL DOSFS_ReadDir( DOS_DIR *dir, LPCSTR *long_name,
478 struct dirent *dirent;
480 #ifdef VFAT_IOCTL_READDIR_BOTH
483 if (ioctl( dir->fd, VFAT_IOCTL_READDIR_BOTH, (long)dir->dirent ) != -1) {
484 if (!dir->dirent[0].d_reclen) return FALSE;
485 if (!DOSFS_ToDosFCBFormat( dir->dirent[0].d_name, dir->short_name ))
486 dir->short_name[0] = '\0';
487 *short_name = dir->short_name;
488 if (dir->dirent[1].d_name[0]) *long_name = dir->dirent[1].d_name;
489 else *long_name = dir->dirent[0].d_name;
493 #endif /* VFAT_IOCTL_READDIR_BOTH */
495 if (!(dirent = readdir( dir->dir ))) return FALSE;
496 *long_name = dirent->d_name;
502 /***********************************************************************
505 * Transform a Unix file name into a hashed DOS name. If the name is a valid
506 * DOS name, it is converted to upper-case; otherwise it is replaced by a
507 * hashed version that fits in 8.3 format.
508 * File name can be terminated by '\0', '\\' or '/'.
509 * 'buffer' must be at least 13 characters long.
511 static void DOSFS_Hash( LPCSTR name, LPSTR buffer, BOOL dir_format,
514 static const char invalid_chars[] = INVALID_DOS_CHARS "~.";
515 static const char hash_chars[32] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345";
522 if (dir_format) strcpy( buffer, " " );
524 if (DOSFS_ValidDOSName( name, ignore_case ))
526 /* Check for '.' and '..' */
530 if (!dir_format) buffer[1] = buffer[2] = '\0';
531 if (name[1] == '.') buffer[1] = '.';
535 /* Simply copy the name, converting to uppercase */
537 for (dst = buffer; !IS_END_OF_NAME(*name) && (*name != '.'); name++)
538 *dst++ = FILE_toupper(*name);
541 if (dir_format) dst = buffer + 8;
543 for (name++; !IS_END_OF_NAME(*name); name++)
544 *dst++ = FILE_toupper(*name);
546 if (!dir_format) *dst = '\0';
550 /* Compute the hash code of the file name */
551 /* If you know something about hash functions, feel free to */
552 /* insert a better algorithm here... */
555 for (p = name, hash = 0xbeef; !IS_END_OF_NAME(p[1]); p++)
556 hash = (hash<<3) ^ (hash>>5) ^ FILE_tolower(*p) ^ (FILE_tolower(p[1]) << 8);
557 hash = (hash<<3) ^ (hash>>5) ^ FILE_tolower(*p); /* Last character*/
561 for (p = name, hash = 0xbeef; !IS_END_OF_NAME(p[1]); p++)
562 hash = (hash << 3) ^ (hash >> 5) ^ *p ^ (p[1] << 8);
563 hash = (hash << 3) ^ (hash >> 5) ^ *p; /* Last character */
566 /* Find last dot for start of the extension */
567 for (p = name+1, ext = NULL; !IS_END_OF_NAME(*p); p++)
568 if (*p == '.') ext = p;
569 if (ext && IS_END_OF_NAME(ext[1]))
570 ext = NULL; /* Empty extension ignored */
572 /* Copy first 4 chars, replacing invalid chars with '_' */
573 for (i = 4, p = name, dst = buffer; i > 0; i--, p++)
575 if (IS_END_OF_NAME(*p) || (p == ext)) break;
576 *dst++ = strchr( invalid_chars, *p ) ? '_' : FILE_toupper(*p);
578 /* Pad to 5 chars with '~' */
579 while (i-- >= 0) *dst++ = '~';
581 /* Insert hash code converted to 3 ASCII chars */
582 *dst++ = hash_chars[(hash >> 10) & 0x1f];
583 *dst++ = hash_chars[(hash >> 5) & 0x1f];
584 *dst++ = hash_chars[hash & 0x1f];
586 /* Copy the first 3 chars of the extension (if any) */
589 if (!dir_format) *dst++ = '.';
590 for (i = 3, ext++; (i > 0) && !IS_END_OF_NAME(*ext); i--, ext++)
591 *dst++ = strchr( invalid_chars, *ext ) ? '_' : FILE_toupper(*ext);
593 if (!dir_format) *dst = '\0';
597 /***********************************************************************
600 * Find the Unix file name in a given directory that corresponds to
601 * a file name (either in Unix or DOS format).
602 * File name can be terminated by '\0', '\\' or '/'.
603 * Return TRUE if OK, FALSE if no file name matches.
605 * 'long_buf' must be at least 'long_len' characters long. If the long name
606 * turns out to be larger than that, the function returns FALSE.
607 * 'short_buf' must be at least 13 characters long.
609 BOOL DOSFS_FindUnixName( LPCSTR path, LPCSTR name, LPSTR long_buf,
610 INT long_len, LPSTR short_buf, BOOL ignore_case)
613 LPCSTR long_name, short_name;
614 char dos_name[12], tmp_buf[13];
617 const char *p = strchr( name, '/' );
618 int len = p ? (int)(p - name) : strlen(name);
619 if ((p = strchr( name, '\\' ))) len = min( (int)(p - name), len );
620 /* Ignore trailing dots and spaces */
621 while (len > 1 && (name[len-1] == '.' || name[len-1] == ' ')) len--;
622 if (long_len < len + 1) return FALSE;
624 TRACE("%s,%s\n", path, name );
626 if (!DOSFS_ToDosFCBFormat( name, dos_name )) dos_name[0] = '\0';
628 if (!(dir = DOSFS_OpenDir( path )))
630 WARN("(%s,%s): can't open dir: %s\n",
631 path, name, strerror(errno) );
635 while ((ret = DOSFS_ReadDir( dir, &long_name, &short_name )))
637 /* Check against Unix name */
638 if (len == strlen(long_name))
642 if (!strncmp( long_name, name, len )) break;
646 if (!FILE_strncasecmp( long_name, name, len )) break;
651 /* Check against hashed DOS name */
654 DOSFS_Hash( long_name, tmp_buf, TRUE, ignore_case );
655 short_name = tmp_buf;
657 if (!strcmp( dos_name, short_name )) break;
662 if (long_buf) strcpy( long_buf, long_name );
666 DOSFS_ToDosDTAFormat( short_name, short_buf );
668 DOSFS_Hash( long_name, short_buf, FALSE, ignore_case );
670 TRACE("(%s,%s) -> %s (%s)\n",
671 path, name, long_name, short_buf ? short_buf : "***");
674 WARN("'%s' not found in '%s'\n", name, path);
675 DOSFS_CloseDir( dir );
680 /***********************************************************************
683 * Check if a DOS file name represents a DOS device and return the device.
685 const DOS_DEVICE *DOSFS_GetDevice( const char *name )
690 if (!name) return NULL; /* if FILE_DupUnixHandle was used */
691 if (name[0] && (name[1] == ':')) name += 2;
692 if ((p = strrchr( name, '/' ))) name = p + 1;
693 if ((p = strrchr( name, '\\' ))) name = p + 1;
694 for (i = 0; i < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0]); i++)
696 const char *dev = DOSFS_Devices[i].name;
697 if (!FILE_strncasecmp( dev, name, strlen(dev) ))
699 p = name + strlen( dev );
700 if (!*p || (*p == '.') || (*p == ':')) return &DOSFS_Devices[i];
707 /***********************************************************************
708 * DOSFS_GetDeviceByHandle
710 const DOS_DEVICE *DOSFS_GetDeviceByHandle( HFILE hFile )
712 const DOS_DEVICE *ret = NULL;
713 SERVER_START_REQ( get_file_info )
716 if (!wine_server_call( req ) && (reply->type == FILE_TYPE_UNKNOWN))
718 if ((reply->attr >= 0) &&
719 (reply->attr < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0])))
720 ret = &DOSFS_Devices[reply->attr];
728 /**************************************************************************
729 * DOSFS_CreateCommPort
731 static HANDLE DOSFS_CreateCommPort(LPCSTR name, DWORD access, DWORD attributes, LPSECURITY_ATTRIBUTES sa)
736 TRACE_(file)("%s %lx %lx\n", name, access, attributes);
738 PROFILE_GetWineIniString("serialports",name,"",devname,sizeof devname);
742 TRACE("opening %s as %s\n", devname, name);
744 SERVER_START_REQ( create_serial )
746 req->access = access;
747 req->inherit = (sa && (sa->nLength>=sizeof(*sa)) && sa->bInheritHandle);
748 req->attributes = attributes;
749 req->sharing = FILE_SHARE_READ|FILE_SHARE_WRITE;
750 wine_server_add_data( req, devname, strlen(devname) );
752 wine_server_call_err( req );
758 ERR("Couldn't open device '%s' ! (check permissions)\n",devname);
760 TRACE("return %08X\n", ret );
764 /***********************************************************************
767 * Open a DOS device. This might not map 1:1 into the UNIX device concept.
768 * Returns 0 on failure.
770 HANDLE DOSFS_OpenDevice( const char *name, DWORD access, DWORD attributes, LPSECURITY_ATTRIBUTES sa )
776 if (name[0] && (name[1] == ':')) name += 2;
777 if ((p = strrchr( name, '/' ))) name = p + 1;
778 if ((p = strrchr( name, '\\' ))) name = p + 1;
779 for (i = 0; i < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0]); i++)
781 const char *dev = DOSFS_Devices[i].name;
782 if (!FILE_strncasecmp( dev, name, strlen(dev) ))
784 p = name + strlen( dev );
785 if (!*p || (*p == '.') || (*p == ':')) {
787 if (!strcmp(DOSFS_Devices[i].name,"NUL"))
788 return FILE_CreateFile( "/dev/null", access,
789 FILE_SHARE_READ|FILE_SHARE_WRITE, sa,
790 OPEN_EXISTING, 0, 0, TRUE, DRIVE_UNKNOWN );
791 if (!strcmp(DOSFS_Devices[i].name,"CON")) {
793 switch (access & (GENERIC_READ|GENERIC_WRITE)) {
795 to_dup = GetStdHandle( STD_INPUT_HANDLE );
798 to_dup = GetStdHandle( STD_OUTPUT_HANDLE );
801 FIXME("can't open CON read/write\n");
804 if (!DuplicateHandle( GetCurrentProcess(), to_dup, GetCurrentProcess(),
806 sa && (sa->nLength>=sizeof(*sa)) && sa->bInheritHandle,
807 DUPLICATE_SAME_ACCESS ))
811 if (!strcmp(DOSFS_Devices[i].name,"SCSIMGR$") ||
812 !strcmp(DOSFS_Devices[i].name,"HPSCAN") ||
813 !strcmp(DOSFS_Devices[i].name,"EMMXXXX0"))
815 return FILE_CreateDevice( i, access, sa );
818 if( (handle=DOSFS_CreateCommPort(DOSFS_Devices[i].name,access,attributes,sa)) )
820 FIXME("device open %s not supported (yet)\n",DOSFS_Devices[i].name);
829 /***********************************************************************
832 * Get the drive specified by a given path name (DOS or Unix format).
834 static int DOSFS_GetPathDrive( const char **name )
837 const char *p = *name;
839 if (*p && (p[1] == ':'))
841 drive = FILE_toupper(*p) - 'A';
844 else if (*p == '/') /* Absolute Unix path? */
846 if ((drive = DRIVE_FindDriveRoot( name )) == -1)
848 MESSAGE("Warning: %s not accessible from a configured DOS drive\n", *name );
849 /* Assume it really was a DOS name */
850 drive = DRIVE_GetCurrentDrive();
853 else drive = DRIVE_GetCurrentDrive();
855 if (!DRIVE_IsValid(drive))
857 SetLastError( ERROR_INVALID_DRIVE );
864 /***********************************************************************
867 * Convert a file name (DOS or mixed DOS/Unix format) to a valid
868 * Unix name / short DOS name pair.
869 * Return FALSE if one of the path components does not exist. The last path
870 * component is only checked if 'check_last' is non-zero.
871 * The buffers pointed to by 'long_buf' and 'short_buf' must be
872 * at least MAX_PATHNAME_LEN long.
874 BOOL DOSFS_GetFullName( LPCSTR name, BOOL check_last, DOS_FULL_NAME *full )
878 char *p_l, *p_s, *root;
880 TRACE("%s (last=%d)\n", name, check_last );
882 if ((!*name) || (*name=='\n'))
883 { /* error code for Win98 */
884 SetLastError(ERROR_BAD_PATHNAME);
888 if ((full->drive = DOSFS_GetPathDrive( &name )) == -1) return FALSE;
889 flags = DRIVE_GetFlags( full->drive );
891 lstrcpynA( full->long_name, DRIVE_GetRoot( full->drive ),
892 sizeof(full->long_name) );
893 if (full->long_name[1]) root = full->long_name + strlen(full->long_name);
894 else root = full->long_name; /* root directory */
896 strcpy( full->short_name, "A:\\" );
897 full->short_name[0] += full->drive;
899 if ((*name == '\\') || (*name == '/')) /* Absolute path */
901 while ((*name == '\\') || (*name == '/')) name++;
903 else /* Relative path */
905 lstrcpynA( root + 1, DRIVE_GetUnixCwd( full->drive ),
906 sizeof(full->long_name) - (root - full->long_name) - 1 );
907 if (root[1]) *root = '/';
908 lstrcpynA( full->short_name + 3, DRIVE_GetDosCwd( full->drive ),
909 sizeof(full->short_name) - 3 );
912 p_l = full->long_name[1] ? full->long_name + strlen(full->long_name)
914 p_s = full->short_name[3] ? full->short_name + strlen(full->short_name)
915 : full->short_name + 2;
918 while (*name && found)
920 /* Check for '.' and '..' */
924 if (IS_END_OF_NAME(name[1]))
927 while ((*name == '\\') || (*name == '/')) name++;
930 else if ((name[1] == '.') && IS_END_OF_NAME(name[2]))
933 while ((*name == '\\') || (*name == '/')) name++;
934 while ((p_l > root) && (*p_l != '/')) p_l--;
935 while ((p_s > full->short_name + 2) && (*p_s != '\\')) p_s--;
936 *p_l = *p_s = '\0'; /* Remove trailing separator */
941 /* Make sure buffers are large enough */
943 if ((p_s >= full->short_name + sizeof(full->short_name) - 14) ||
944 (p_l >= full->long_name + sizeof(full->long_name) - 1))
946 SetLastError( ERROR_PATH_NOT_FOUND );
950 /* Get the long and short name matching the file name */
952 if ((found = DOSFS_FindUnixName( full->long_name, name, p_l + 1,
953 sizeof(full->long_name) - (p_l - full->long_name) - 1,
954 p_s + 1, !(flags & DRIVE_CASE_SENSITIVE) )))
960 while (!IS_END_OF_NAME(*name)) name++;
962 else if (!check_last)
966 while (!IS_END_OF_NAME(*name) &&
967 (p_s < full->short_name + sizeof(full->short_name) - 1) &&
968 (p_l < full->long_name + sizeof(full->long_name) - 1))
970 *p_s++ = FILE_tolower(*name);
971 /* If the drive is case-sensitive we want to create new */
972 /* files in lower-case otherwise we can't reopen them */
973 /* under the same short name. */
974 if (flags & DRIVE_CASE_SENSITIVE) *p_l++ = FILE_tolower(*name);
978 /* Ignore trailing dots and spaces */
979 while(p_l[-1] == '.' || p_l[-1] == ' ') {
985 while ((*name == '\\') || (*name == '/')) name++;
992 SetLastError( ERROR_FILE_NOT_FOUND );
995 if (*name) /* Not last */
997 SetLastError( ERROR_PATH_NOT_FOUND );
1001 if (!full->long_name[0]) strcpy( full->long_name, "/" );
1002 if (!full->short_name[2]) strcpy( full->short_name + 2, "\\" );
1003 TRACE("returning %s = %s\n", full->long_name, full->short_name );
1008 /***********************************************************************
1009 * GetShortPathNameA (KERNEL32.@)
1013 * longpath=NULL: LastError=ERROR_INVALID_PARAMETER, ret=0
1014 * longpath="" or invalid: LastError=ERROR_BAD_PATHNAME, ret=0
1016 * more observations ( with NT 3.51 (WinDD) ):
1017 * longpath <= 8.3 -> just copy longpath to shortpath
1019 * a) file does not exist -> return 0, LastError = ERROR_FILE_NOT_FOUND
1020 * b) file does exist -> set the short filename.
1021 * - trailing slashes are reproduced in the short name, even if the
1022 * file is not a directory
1023 * - the absolute/relative path of the short name is reproduced like found
1025 * - longpath and shortpath may have the same address
1026 * Peter Ganten, 1999
1028 DWORD WINAPI GetShortPathNameA( LPCSTR longpath, LPSTR shortpath,
1031 DOS_FULL_NAME full_name;
1033 DWORD sp = 0, lp = 0;
1036 BOOL unixabsolute = *longpath == '/';
1038 TRACE("%s\n", debugstr_a(longpath));
1041 SetLastError(ERROR_INVALID_PARAMETER);
1045 SetLastError(ERROR_BAD_PATHNAME);
1049 if ( ( tmpshortpath = HeapAlloc ( GetProcessHeap(), 0, MAX_PATHNAME_LEN ) ) == NULL ) {
1050 SetLastError ( ERROR_NOT_ENOUGH_MEMORY );
1054 /* check for drive letter */
1055 if (!unixabsolute && longpath[1] == ':' ) {
1056 tmpshortpath[0] = longpath[0];
1057 tmpshortpath[1] = ':';
1061 if ( ( drive = DOSFS_GetPathDrive ( &longpath )) == -1 ) return 0;
1062 flags = DRIVE_GetFlags ( drive );
1064 if (unixabsolute && drive != DRIVE_GetCurrentDrive()) {
1065 tmpshortpath[0] = drive + 'A';
1066 tmpshortpath[1] = ':';
1070 while ( longpath[lp] ) {
1072 /* check for path delimiters and reproduce them */
1073 if ( longpath[lp] == '\\' || longpath[lp] == '/' ) {
1074 if (!sp || tmpshortpath[sp-1]!= '\\')
1076 /* strip double "\\" */
1077 tmpshortpath[sp] = '\\';
1080 tmpshortpath[sp]=0;/*terminate string*/
1085 tmplen = strcspn ( longpath + lp, "\\/" );
1086 lstrcpynA ( tmpshortpath+sp, longpath + lp, tmplen+1 );
1088 /* Check, if the current element is a valid dos name */
1089 if ( DOSFS_ValidDOSName ( longpath + lp, !(flags & DRIVE_CASE_SENSITIVE) ) ) {
1095 /* Check if the file exists and use the existing file name */
1096 if ( DOSFS_GetFullName ( tmpshortpath, TRUE, &full_name ) ) {
1097 strcpy( tmpshortpath+sp, strrchr ( full_name.short_name, '\\' ) + 1 );
1098 sp += strlen ( tmpshortpath+sp );
1103 TRACE("not found!\n" );
1104 SetLastError ( ERROR_FILE_NOT_FOUND );
1107 tmpshortpath[sp] = 0;
1109 lstrcpynA ( shortpath, tmpshortpath, shortlen );
1110 TRACE("returning %s\n", debugstr_a(shortpath) );
1111 tmplen = strlen ( tmpshortpath );
1112 HeapFree ( GetProcessHeap(), 0, tmpshortpath );
1118 /***********************************************************************
1119 * GetShortPathNameW (KERNEL32.@)
1121 DWORD WINAPI GetShortPathNameW( LPCWSTR longpath, LPWSTR shortpath,
1124 LPSTR longpathA, shortpathA;
1127 longpathA = HEAP_strdupWtoA( GetProcessHeap(), 0, longpath );
1128 shortpathA = HeapAlloc ( GetProcessHeap(), 0, shortlen );
1130 ret = GetShortPathNameA ( longpathA, shortpathA, shortlen );
1131 if (shortlen > 0 && !MultiByteToWideChar( CP_ACP, 0, shortpathA, -1, shortpath, shortlen ))
1132 shortpath[shortlen-1] = 0;
1133 HeapFree( GetProcessHeap(), 0, longpathA );
1134 HeapFree( GetProcessHeap(), 0, shortpathA );
1140 /***********************************************************************
1141 * GetLongPathNameA (KERNEL32.@)
1144 * observed (Win2000):
1145 * shortpath=NULL: LastError=ERROR_INVALID_PARAMETER, ret=0
1146 * shortpath="": LastError=ERROR_PATH_NOT_FOUND, ret=0
1148 DWORD WINAPI GetLongPathNameA( LPCSTR shortpath, LPSTR longpath,
1151 DOS_FULL_NAME full_name;
1152 char *p, *r, *ll, *ss;
1155 SetLastError(ERROR_INVALID_PARAMETER);
1158 if (!shortpath[0]) {
1159 SetLastError(ERROR_PATH_NOT_FOUND);
1163 if(shortpath[0]=='\\' && shortpath[1]=='\\')
1165 ERR("UNC pathname %s\n",debugstr_a(shortpath));
1166 lstrcpynA( longpath, full_name.short_name, longlen );
1167 return lstrlenA(longpath);
1170 if (!DOSFS_GetFullName( shortpath, TRUE, &full_name )) return 0;
1171 lstrcpynA( longpath, full_name.short_name, longlen );
1173 /* Do some hackery to get the long filename. */
1176 ss=longpath+strlen(longpath);
1177 ll=full_name.long_name+strlen(full_name.long_name);
1179 while (ss>=longpath)
1181 /* FIXME: aren't we more paranoid, than needed? */
1182 while ((ss[0]=='\\') && (ss>=longpath)) ss--;
1184 while ((ss[0]!='\\') && (ss>=longpath)) ss--;
1187 /* FIXME: aren't we more paranoid, than needed? */
1188 while ((ll[0]=='/') && (ll>=full_name.long_name)) ll--;
1189 while ((ll[0]!='/') && (ll>=full_name.long_name)) ll--;
1190 if (ll<full_name.long_name)
1192 ERR("Bad longname! (ss=%s ll=%s)\n This should never happen !\n"
1199 /* FIXME: fix for names like "C:\\" (ie. with more '\'s) */
1203 if ((p-longpath)>0) longlen -= (p-longpath);
1204 lstrcpynA( p, ll , longlen);
1206 /* Now, change all '/' to '\' */
1207 for (r=p; r<(p+longlen); r++ )
1208 if (r[0]=='/') r[0]='\\';
1209 return strlen(longpath) - strlen(p) + longlen;
1213 return strlen(longpath);
1217 /***********************************************************************
1218 * GetLongPathNameW (KERNEL32.@)
1220 DWORD WINAPI GetLongPathNameW( LPCWSTR shortpath, LPWSTR longpath,
1223 DOS_FULL_NAME full_name;
1225 LPSTR shortpathA = HEAP_strdupWtoA( GetProcessHeap(), 0, shortpath );
1227 /* FIXME: is it correct to always return a fully qualified short path? */
1228 if (DOSFS_GetFullName( shortpathA, TRUE, &full_name ))
1230 ret = strlen( full_name.short_name );
1231 if (longlen > 0 && !MultiByteToWideChar( CP_ACP, 0, full_name.long_name, -1,
1232 longpath, longlen ))
1233 longpath[longlen-1] = 0;
1235 HeapFree( GetProcessHeap(), 0, shortpathA );
1240 /***********************************************************************
1241 * DOSFS_DoGetFullPathName
1243 * Implementation of GetFullPathNameA/W.
1245 * bon@elektron 000331:
1246 * A test for GetFullPathName with many pathological cases
1247 * now gives identical output for Wine and OSR2
1249 static DWORD DOSFS_DoGetFullPathName( LPCSTR name, DWORD len, LPSTR result,
1253 DOS_FULL_NAME full_name;
1256 char drivecur[]="c:.";
1258 int namelen,drive=0;
1260 if (!name[0]) return 0;
1262 TRACE("passed '%s'\n", name);
1265 /*drive letter given */
1267 driveletter = name[0];
1269 if ((name[1]==':') && ((name[2]=='\\') || (name[2]=='/')))
1270 /*absolute path given */
1272 lstrcpynA(full_name.short_name,name,MAX_PATHNAME_LEN);
1273 drive = (int)FILE_toupper(name[0]) - 'A';
1278 drivecur[0]=driveletter;
1279 else if ((name[0]=='\\') || (name[0]=='/'))
1280 strcpy(drivecur,"\\");
1282 strcpy(drivecur,".");
1284 if (!DOSFS_GetFullName( drivecur, FALSE, &full_name ))
1286 FIXME("internal: error getting drive/path\n");
1289 /* find path that drive letter substitutes*/
1290 drive = (int)FILE_toupper(full_name.short_name[0]) -0x41;
1291 root= DRIVE_GetRoot(drive);
1294 FIXME("internal: error getting DOS Drive Root\n");
1297 if (!strcmp(root,"/"))
1299 /* we have just the last / and we need it. */
1300 p= full_name.long_name;
1304 p= full_name.long_name +strlen(root);
1306 /* append long name (= unix name) to drive */
1307 lstrcpynA(full_name.short_name+2,p,MAX_PATHNAME_LEN-3);
1308 /* append name to treat */
1309 namelen= strlen(full_name.short_name);
1312 p += +2; /* skip drive name when appending */
1313 if (namelen +2 + strlen(p) > MAX_PATHNAME_LEN)
1315 FIXME("internal error: buffer too small\n");
1318 full_name.short_name[namelen++] ='\\';
1319 full_name.short_name[namelen] = 0;
1320 lstrcpynA(full_name.short_name +namelen,p,MAX_PATHNAME_LEN-namelen);
1322 /* reverse all slashes */
1323 for (p=full_name.short_name;
1324 p < full_name.short_name+strlen(full_name.short_name);
1330 /* Use memmove, as areas overlap */
1332 while ((p = strstr(full_name.short_name,"\\..\\")))
1334 if (p > full_name.short_name+2)
1337 q = strrchr(full_name.short_name,'\\');
1338 memmove(q+1,p+4,strlen(p+4)+1);
1342 memmove(full_name.short_name+3,p+4,strlen(p+4)+1);
1345 if ((full_name.short_name[2]=='.')&&(full_name.short_name[3]=='.'))
1347 /* This case istn't treated yet : c:..\test */
1348 memmove(full_name.short_name+2,full_name.short_name+4,
1349 strlen(full_name.short_name+4)+1);
1352 while ((p = strstr(full_name.short_name,"\\.\\")))
1355 memmove(p+1,p+3,strlen(p+3)+1);
1357 if (!(DRIVE_GetFlags(drive) & DRIVE_CASE_PRESERVING))
1358 for (p = full_name.short_name; *p; p++) *p = FILE_toupper(*p);
1359 namelen=strlen(full_name.short_name);
1360 if (!strcmp(full_name.short_name+namelen-3,"\\.."))
1362 /* one more strange case: "c:\test\test1\.."
1364 *(full_name.short_name+namelen-3)=0;
1365 q = strrchr(full_name.short_name,'\\');
1368 if (full_name.short_name[namelen-1]=='.')
1369 full_name.short_name[(namelen--)-1] =0;
1371 if (full_name.short_name[namelen-1]=='\\')
1372 full_name.short_name[(namelen--)-1] =0;
1373 TRACE("got %s\n",full_name.short_name);
1375 /* If the lpBuffer buffer is too small, the return value is the
1376 size of the buffer, in characters, required to hold the path
1377 plus the terminating \0 (tested against win95osr2, bon 001118)
1379 ret = strlen(full_name.short_name);
1382 /* don't touch anything when the buffer is not large enough */
1383 SetLastError( ERROR_INSUFFICIENT_BUFFER );
1389 MultiByteToWideChar( CP_ACP, 0, full_name.short_name, -1, (LPWSTR)result, len );
1391 lstrcpynA( result, full_name.short_name, len );
1394 TRACE("returning '%s'\n", full_name.short_name );
1399 /***********************************************************************
1400 * GetFullPathNameA (KERNEL32.@)
1402 * if the path closed with '\', *lastpart is 0
1404 DWORD WINAPI GetFullPathNameA( LPCSTR name, DWORD len, LPSTR buffer,
1407 DWORD ret = DOSFS_DoGetFullPathName( name, len, buffer, FALSE );
1408 if (ret && (ret<=len) && buffer && lastpart)
1410 LPSTR p = buffer + strlen(buffer);
1414 while ((p > buffer + 2) && (*p != '\\')) p--;
1417 else *lastpart = NULL;
1423 /***********************************************************************
1424 * GetFullPathNameW (KERNEL32.@)
1426 DWORD WINAPI GetFullPathNameW( LPCWSTR name, DWORD len, LPWSTR buffer,
1429 LPSTR nameA = HEAP_strdupWtoA( GetProcessHeap(), 0, name );
1430 DWORD ret = DOSFS_DoGetFullPathName( nameA, len, (LPSTR)buffer, TRUE );
1431 HeapFree( GetProcessHeap(), 0, nameA );
1432 if (ret && (ret<=len) && buffer && lastpart)
1434 LPWSTR p = buffer + strlenW(buffer);
1435 if (*p != (WCHAR)'\\')
1437 while ((p > buffer + 2) && (*p != (WCHAR)'\\')) p--;
1440 else *lastpart = NULL;
1446 /***********************************************************************
1447 * wine_get_unix_file_name (KERNEL32.@) Not a Windows API
1449 * Return the full Unix file name for a given path.
1451 BOOL WINAPI wine_get_unix_file_name( LPCSTR dos, LPSTR buffer, DWORD len )
1455 if ((ret = DOSFS_GetFullName( dos, FALSE, &path ))) lstrcpynA( buffer, path.long_name, len );
1460 /***********************************************************************
1463 static int DOSFS_FindNextEx( FIND_FIRST_INFO *info, WIN32_FIND_DATAA *entry )
1465 DWORD attr = info->attr | FA_UNUSED | FA_ARCHIVE | FA_RDONLY | FILE_ATTRIBUTE_SYMLINK;
1466 UINT flags = DRIVE_GetFlags( info->drive );
1467 char *p, buffer[MAX_PATHNAME_LEN];
1468 const char *drive_path;
1470 LPCSTR long_name, short_name;
1471 BY_HANDLE_FILE_INFORMATION fileinfo;
1474 if ((info->attr & ~(FA_UNUSED | FA_ARCHIVE | FA_RDONLY)) == FA_LABEL)
1476 if (info->cur_pos) return 0;
1477 entry->dwFileAttributes = FILE_ATTRIBUTE_LABEL;
1478 RtlSecondsSince1970ToTime( (time_t)0, &entry->ftCreationTime );
1479 RtlSecondsSince1970ToTime( (time_t)0, &entry->ftLastAccessTime );
1480 RtlSecondsSince1970ToTime( (time_t)0, &entry->ftLastWriteTime );
1481 entry->nFileSizeHigh = 0;
1482 entry->nFileSizeLow = 0;
1483 entry->dwReserved0 = 0;
1484 entry->dwReserved1 = 0;
1485 DOSFS_ToDosDTAFormat( DRIVE_GetLabel( info->drive ), entry->cFileName );
1486 strcpy( entry->cAlternateFileName, entry->cFileName );
1488 TRACE("returning %s (%s) as label\n",
1489 entry->cFileName, entry->cAlternateFileName);
1493 drive_path = info->path + strlen(DRIVE_GetRoot( info->drive ));
1494 while ((*drive_path == '/') || (*drive_path == '\\')) drive_path++;
1495 drive_root = !*drive_path;
1497 lstrcpynA( buffer, info->path, sizeof(buffer) - 1 );
1498 strcat( buffer, "/" );
1499 p = buffer + strlen(buffer);
1501 while (DOSFS_ReadDir( info->u.dos_dir, &long_name, &short_name ))
1505 /* Don't return '.' and '..' in the root of the drive */
1506 if (drive_root && (long_name[0] == '.') &&
1507 (!long_name[1] || ((long_name[1] == '.') && !long_name[2])))
1510 /* Check the long mask */
1512 if (info->long_mask)
1514 if (!DOSFS_MatchLong( info->long_mask, long_name,
1515 flags & DRIVE_CASE_SENSITIVE )) continue;
1518 /* Check the short mask */
1520 if (info->short_mask)
1524 DOSFS_Hash( long_name, dos_name, TRUE,
1525 !(flags & DRIVE_CASE_SENSITIVE) );
1526 short_name = dos_name;
1528 if (!DOSFS_MatchShort( info->short_mask, short_name )) continue;
1531 /* Check the file attributes */
1533 lstrcpynA( p, long_name, sizeof(buffer) - (int)(p - buffer) );
1534 if (!FILE_Stat( buffer, &fileinfo ))
1536 WARN("can't stat %s\n", buffer);
1539 if ((fileinfo.dwFileAttributes & FILE_ATTRIBUTE_SYMLINK) &&
1540 (fileinfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
1542 static int show_dir_symlinks = -1;
1543 if (show_dir_symlinks == -1)
1544 show_dir_symlinks = PROFILE_GetWineIniBool("wine", "ShowDirSymlinks", 0);
1545 if (!show_dir_symlinks) continue;
1548 if (fileinfo.dwFileAttributes & ~attr) continue;
1550 /* We now have a matching entry; fill the result and return */
1552 entry->dwFileAttributes = fileinfo.dwFileAttributes;
1553 entry->ftCreationTime = fileinfo.ftCreationTime;
1554 entry->ftLastAccessTime = fileinfo.ftLastAccessTime;
1555 entry->ftLastWriteTime = fileinfo.ftLastWriteTime;
1556 entry->nFileSizeHigh = fileinfo.nFileSizeHigh;
1557 entry->nFileSizeLow = fileinfo.nFileSizeLow;
1560 DOSFS_ToDosDTAFormat( short_name, entry->cAlternateFileName );
1562 DOSFS_Hash( long_name, entry->cAlternateFileName, FALSE,
1563 !(flags & DRIVE_CASE_SENSITIVE) );
1565 lstrcpynA( entry->cFileName, long_name, sizeof(entry->cFileName) );
1566 if (!(flags & DRIVE_CASE_PRESERVING)) _strlwr( entry->cFileName );
1567 TRACE("returning %s (%s) %02lx %ld\n",
1568 entry->cFileName, entry->cAlternateFileName,
1569 entry->dwFileAttributes, entry->nFileSizeLow );
1572 return 0; /* End of directory */
1575 /***********************************************************************
1578 * Find the next matching file. Return the number of entries read to find
1579 * the matching one, or 0 if no more entries.
1580 * 'short_mask' is the 8.3 mask (in FCB format), 'long_mask' is the long
1581 * file name mask. Either or both can be NULL.
1583 * NOTE: This is supposed to be only called by the int21 emulation
1584 * routines. Thus, we should own the Win16Mutex anyway.
1585 * Nevertheless, we explicitly enter it to ensure the static
1586 * directory cache is protected.
1588 int DOSFS_FindNext( const char *path, const char *short_mask,
1589 const char *long_mask, int drive, BYTE attr,
1590 int skip, WIN32_FIND_DATAA *entry )
1592 static FIND_FIRST_INFO info;
1593 LPCSTR short_name, long_name;
1598 /* Check the cached directory */
1599 if (!(info.u.dos_dir && info.path == path && info.short_mask == short_mask
1600 && info.long_mask == long_mask && info.drive == drive
1601 && info.attr == attr && info.cur_pos <= skip))
1603 /* Not in the cache, open it anew */
1604 if (info.u.dos_dir) DOSFS_CloseDir( info.u.dos_dir );
1606 info.path = (LPSTR)path;
1607 info.long_mask = (LPSTR)long_mask;
1608 info.short_mask = (LPSTR)short_mask;
1612 info.u.dos_dir = DOSFS_OpenDir( info.path );
1615 /* Skip to desired position */
1616 while (info.cur_pos < skip)
1617 if (info.u.dos_dir && DOSFS_ReadDir( info.u.dos_dir, &long_name, &short_name ))
1622 if (info.u.dos_dir && info.cur_pos == skip && DOSFS_FindNextEx( &info, entry ))
1623 count = info.cur_pos - skip;
1629 if (info.u.dos_dir) DOSFS_CloseDir( info.u.dos_dir );
1630 memset( &info, '\0', sizeof(info) );
1638 /*************************************************************************
1639 * FindFirstFileExA (KERNEL32.@)
1641 HANDLE WINAPI FindFirstFileExA(
1643 FINDEX_INFO_LEVELS fInfoLevelId,
1644 LPVOID lpFindFileData,
1645 FINDEX_SEARCH_OPS fSearchOp,
1646 LPVOID lpSearchFilter,
1647 DWORD dwAdditionalFlags)
1650 FIND_FIRST_INFO *info;
1652 if ((fSearchOp != FindExSearchNameMatch) || (dwAdditionalFlags != 0))
1654 FIXME("options not implemented 0x%08x 0x%08lx\n", fSearchOp, dwAdditionalFlags );
1655 return INVALID_HANDLE_VALUE;
1658 switch(fInfoLevelId)
1660 case FindExInfoStandard:
1662 WIN32_FIND_DATAA * data = (WIN32_FIND_DATAA *) lpFindFileData;
1663 data->dwReserved0 = data->dwReserved1 = 0x0;
1664 if (!lpFileName) return 0;
1665 if (lpFileName[0] == '\\' && lpFileName[1] == '\\')
1667 ERR("UNC path name\n");
1668 if (!(handle = GlobalAlloc(GMEM_MOVEABLE, sizeof(FIND_FIRST_INFO)))) break;
1670 info = (FIND_FIRST_INFO *)GlobalLock( handle );
1671 info->u.smb_dir = SMB_FindFirst(lpFileName);
1672 if(info->u.smb_dir < 0)
1674 GlobalUnlock( handle );
1681 GlobalUnlock( handle );
1685 DOS_FULL_NAME full_name;
1687 if (!DOSFS_GetFullName( lpFileName, FALSE, &full_name )) break;
1688 if (!(handle = GlobalAlloc(GMEM_MOVEABLE, sizeof(FIND_FIRST_INFO)))) break;
1689 info = (FIND_FIRST_INFO *)GlobalLock( handle );
1690 info->path = HeapAlloc( GetProcessHeap(), 0, strlen(full_name.long_name)+1 );
1691 strcpy( info->path, full_name.long_name );
1692 info->long_mask = strrchr( info->path, '/' );
1693 *(info->long_mask++) = '\0';
1694 info->short_mask = NULL;
1696 if (lpFileName[0] && (lpFileName[1] == ':'))
1697 info->drive = FILE_toupper(*lpFileName) - 'A';
1698 else info->drive = DRIVE_GetCurrentDrive();
1701 info->u.dos_dir = DOSFS_OpenDir( info->path );
1702 GlobalUnlock( handle );
1705 if (!FindNextFileA( handle, data ))
1707 FindClose( handle );
1708 SetLastError( ERROR_NO_MORE_FILES );
1715 FIXME("fInfoLevelId 0x%08x not implemented\n", fInfoLevelId );
1717 return INVALID_HANDLE_VALUE;
1720 /*************************************************************************
1721 * FindFirstFileA (KERNEL32.@)
1723 HANDLE WINAPI FindFirstFileA(
1725 WIN32_FIND_DATAA *lpFindData )
1727 return FindFirstFileExA(lpFileName, FindExInfoStandard, lpFindData,
1728 FindExSearchNameMatch, NULL, 0);
1731 /*************************************************************************
1732 * FindFirstFileExW (KERNEL32.@)
1734 HANDLE WINAPI FindFirstFileExW(
1736 FINDEX_INFO_LEVELS fInfoLevelId,
1737 LPVOID lpFindFileData,
1738 FINDEX_SEARCH_OPS fSearchOp,
1739 LPVOID lpSearchFilter,
1740 DWORD dwAdditionalFlags)
1743 WIN32_FIND_DATAA dataA;
1744 LPVOID _lpFindFileData;
1747 switch(fInfoLevelId)
1749 case FindExInfoStandard:
1751 _lpFindFileData = &dataA;
1755 FIXME("fInfoLevelId 0x%08x not implemented\n", fInfoLevelId );
1756 return INVALID_HANDLE_VALUE;
1759 pathA = HEAP_strdupWtoA( GetProcessHeap(), 0, lpFileName );
1760 handle = FindFirstFileExA(pathA, fInfoLevelId, _lpFindFileData, fSearchOp, lpSearchFilter, dwAdditionalFlags);
1761 HeapFree( GetProcessHeap(), 0, pathA );
1762 if (handle == INVALID_HANDLE_VALUE) return handle;
1764 switch(fInfoLevelId)
1766 case FindExInfoStandard:
1768 WIN32_FIND_DATAW *dataW = (WIN32_FIND_DATAW*) lpFindFileData;
1769 dataW->dwFileAttributes = dataA.dwFileAttributes;
1770 dataW->ftCreationTime = dataA.ftCreationTime;
1771 dataW->ftLastAccessTime = dataA.ftLastAccessTime;
1772 dataW->ftLastWriteTime = dataA.ftLastWriteTime;
1773 dataW->nFileSizeHigh = dataA.nFileSizeHigh;
1774 dataW->nFileSizeLow = dataA.nFileSizeLow;
1775 MultiByteToWideChar( CP_ACP, 0, dataA.cFileName, -1,
1776 dataW->cFileName, sizeof(dataW->cFileName)/sizeof(WCHAR) );
1777 MultiByteToWideChar( CP_ACP, 0, dataA.cAlternateFileName, -1,
1778 dataW->cAlternateFileName,
1779 sizeof(dataW->cAlternateFileName)/sizeof(WCHAR) );
1783 FIXME("fInfoLevelId 0x%08x not implemented\n", fInfoLevelId );
1784 return INVALID_HANDLE_VALUE;
1789 /*************************************************************************
1790 * FindFirstFileW (KERNEL32.@)
1792 HANDLE WINAPI FindFirstFileW( LPCWSTR lpFileName, WIN32_FIND_DATAW *lpFindData )
1794 return FindFirstFileExW(lpFileName, FindExInfoStandard, lpFindData,
1795 FindExSearchNameMatch, NULL, 0);
1798 /*************************************************************************
1799 * FindNextFileA (KERNEL32.@)
1801 BOOL WINAPI FindNextFileA( HANDLE handle, WIN32_FIND_DATAA *data )
1803 FIND_FIRST_INFO *info;
1805 DWORD gle = ERROR_NO_MORE_FILES;
1807 if ((handle == INVALID_HANDLE_VALUE) ||
1808 !(info = (FIND_FIRST_INFO *)GlobalLock( handle )))
1810 SetLastError( ERROR_INVALID_HANDLE );
1813 if (info->drive == -1)
1815 ret = SMB_FindNext( info->u.smb_dir, data );
1818 SMB_CloseDir( info->u.smb_dir );
1819 HeapFree( GetProcessHeap(), 0, info->path );
1823 else if (!info->path || !info->u.dos_dir)
1827 else if (!DOSFS_FindNextEx( info, data ))
1829 DOSFS_CloseDir( info->u.dos_dir ); info->u.dos_dir = NULL;
1830 HeapFree( GetProcessHeap(), 0, info->path );
1831 info->path = info->long_mask = NULL;
1836 GlobalUnlock( handle );
1837 if( !ret ) SetLastError( gle );
1842 /*************************************************************************
1843 * FindNextFileW (KERNEL32.@)
1845 BOOL WINAPI FindNextFileW( HANDLE handle, WIN32_FIND_DATAW *data )
1847 WIN32_FIND_DATAA dataA;
1848 if (!FindNextFileA( handle, &dataA )) return FALSE;
1849 data->dwFileAttributes = dataA.dwFileAttributes;
1850 data->ftCreationTime = dataA.ftCreationTime;
1851 data->ftLastAccessTime = dataA.ftLastAccessTime;
1852 data->ftLastWriteTime = dataA.ftLastWriteTime;
1853 data->nFileSizeHigh = dataA.nFileSizeHigh;
1854 data->nFileSizeLow = dataA.nFileSizeLow;
1855 MultiByteToWideChar( CP_ACP, 0, dataA.cFileName, -1,
1856 data->cFileName, sizeof(data->cFileName)/sizeof(WCHAR) );
1857 MultiByteToWideChar( CP_ACP, 0, dataA.cAlternateFileName, -1,
1858 data->cAlternateFileName,
1859 sizeof(data->cAlternateFileName)/sizeof(WCHAR) );
1863 /*************************************************************************
1864 * FindClose (KERNEL32.@)
1866 BOOL WINAPI FindClose( HANDLE handle )
1868 FIND_FIRST_INFO *info;
1870 if (handle == INVALID_HANDLE_VALUE) goto error;
1874 if ((info = (FIND_FIRST_INFO *)GlobalLock( handle )))
1876 if (info->u.dos_dir) DOSFS_CloseDir( info->u.dos_dir );
1877 if (info->path) HeapFree( GetProcessHeap(), 0, info->path );
1880 __EXCEPT(page_fault)
1882 WARN("Illegal handle %x\n", handle);
1883 SetLastError( ERROR_INVALID_HANDLE );
1887 if (!info) goto error;
1888 GlobalUnlock( handle );
1889 GlobalFree( handle );
1893 SetLastError( ERROR_INVALID_HANDLE );
1897 /***********************************************************************
1898 * DOSFS_UnixTimeToFileTime
1900 * Convert a Unix time to FILETIME format.
1901 * The FILETIME structure is a 64-bit value representing the number of
1902 * 100-nanosecond intervals since January 1, 1601, 0:00.
1903 * 'remainder' is the nonnegative number of 100-ns intervals
1904 * corresponding to the time fraction smaller than 1 second that
1905 * couldn't be stored in the time_t value.
1907 void DOSFS_UnixTimeToFileTime( time_t unix_time, FILETIME *filetime,
1913 The time difference between 1 January 1601, 00:00:00 and
1914 1 January 1970, 00:00:00 is 369 years, plus the leap years
1915 from 1604 to 1968, excluding 1700, 1800, 1900.
1916 This makes (1968 - 1600) / 4 - 3 = 89 leap days, and a total
1919 Any day in that period had 24 * 60 * 60 = 86400 seconds.
1921 The time difference is 134774 * 86400 * 10000000, which can be written
1923 27111902 * 2^32 + 3577643008
1924 413 * 2^48 + 45534 * 2^32 + 54590 * 2^16 + 32768
1926 If you find that these constants are buggy, please change them in all
1927 instances in both conversion functions.
1930 There are two versions, one of them uses long long variables and
1931 is presumably faster but not ISO C. The other one uses standard C
1932 data types and operations but relies on the assumption that negative
1933 numbers are stored as 2's complement (-1 is 0xffff....). If this
1934 assumption is violated, dates before 1970 will not convert correctly.
1935 This should however work on any reasonable architecture where WINE
1940 Take care not to remove the casts. I have tested these functions
1941 (in both versions) for a lot of numbers. I would be interested in
1942 results on other compilers than GCC.
1944 The operations have been designed to account for the possibility
1945 of 64-bit time_t in future UNICES. Even the versions without
1946 internal long long numbers will work if time_t only is 64 bit.
1947 A 32-bit shift, which was necessary for that operation, turned out
1948 not to work correctly in GCC, besides giving the warning. So I
1949 used a double 16-bit shift instead. Numbers are in the ISO version
1950 represented by three limbs, the most significant with 32 bit, the
1951 other two with 16 bit each.
1953 As the modulo-operator % is not well-defined for negative numbers,
1954 negative divisors have been avoided in DOSFS_FileTimeToUnixTime.
1956 There might be quicker ways to do this in C. Certainly so in
1959 Claus Fischer, fischer@iue.tuwien.ac.at
1962 #if SIZEOF_LONG_LONG >= 8
1963 # define USE_LONG_LONG 1
1965 # define USE_LONG_LONG 0
1968 #if USE_LONG_LONG /* gcc supports long long type */
1970 long long int t = unix_time;
1972 t += 116444736000000000LL;
1974 filetime->dwLowDateTime = (UINT)t;
1975 filetime->dwHighDateTime = (UINT)(t >> 32);
1977 #else /* ISO version */
1979 UINT a0; /* 16 bit, low bits */
1980 UINT a1; /* 16 bit, medium bits */
1981 UINT a2; /* 32 bit, high bits */
1983 /* Copy the unix time to a2/a1/a0 */
1984 a0 = unix_time & 0xffff;
1985 a1 = (unix_time >> 16) & 0xffff;
1986 /* This is obsolete if unix_time is only 32 bits, but it does not hurt.
1987 Do not replace this by >> 32, it gives a compiler warning and it does
1989 a2 = (unix_time >= 0 ? (unix_time >> 16) >> 16 :
1990 ~((~unix_time >> 16) >> 16));
1992 /* Multiply a by 10000000 (a = a2/a1/a0)
1993 Split the factor into 10000 * 1000 which are both less than 0xffff. */
1995 a1 = a1 * 10000 + (a0 >> 16);
1996 a2 = a2 * 10000 + (a1 >> 16);
2001 a1 = a1 * 1000 + (a0 >> 16);
2002 a2 = a2 * 1000 + (a1 >> 16);
2006 /* Add the time difference and the remainder */
2007 a0 += 32768 + (remainder & 0xffff);
2008 a1 += 54590 + (remainder >> 16 ) + (a0 >> 16);
2009 a2 += 27111902 + (a1 >> 16);
2014 filetime->dwLowDateTime = (a1 << 16) + a0;
2015 filetime->dwHighDateTime = a2;
2020 /***********************************************************************
2021 * DOSFS_FileTimeToUnixTime
2023 * Convert a FILETIME format to Unix time.
2024 * If not NULL, 'remainder' contains the fractional part of the filetime,
2025 * in the range of [0..9999999] (even if time_t is negative).
2027 time_t DOSFS_FileTimeToUnixTime( const FILETIME *filetime, DWORD *remainder )
2029 /* Read the comment in the function DOSFS_UnixTimeToFileTime. */
2032 long long int t = filetime->dwHighDateTime;
2034 t += (UINT)filetime->dwLowDateTime;
2035 t -= 116444736000000000LL;
2038 if (remainder) *remainder = 9999999 - (-t - 1) % 10000000;
2039 return -1 - ((-t - 1) / 10000000);
2043 if (remainder) *remainder = t % 10000000;
2044 return t / 10000000;
2047 #else /* ISO version */
2049 UINT a0; /* 16 bit, low bits */
2050 UINT a1; /* 16 bit, medium bits */
2051 UINT a2; /* 32 bit, high bits */
2052 UINT r; /* remainder of division */
2053 unsigned int carry; /* carry bit for subtraction */
2054 int negative; /* whether a represents a negative value */
2056 /* Copy the time values to a2/a1/a0 */
2057 a2 = (UINT)filetime->dwHighDateTime;
2058 a1 = ((UINT)filetime->dwLowDateTime ) >> 16;
2059 a0 = ((UINT)filetime->dwLowDateTime ) & 0xffff;
2061 /* Subtract the time difference */
2062 if (a0 >= 32768 ) a0 -= 32768 , carry = 0;
2063 else a0 += (1 << 16) - 32768 , carry = 1;
2065 if (a1 >= 54590 + carry) a1 -= 54590 + carry, carry = 0;
2066 else a1 += (1 << 16) - 54590 - carry, carry = 1;
2068 a2 -= 27111902 + carry;
2070 /* If a is negative, replace a by (-1-a) */
2071 negative = (a2 >= ((UINT)1) << 31);
2074 /* Set a to -a - 1 (a is a2/a1/a0) */
2080 /* Divide a by 10000000 (a = a2/a1/a0), put the rest into r.
2081 Split the divisor into 10000 * 1000 which are both less than 0xffff. */
2082 a1 += (a2 % 10000) << 16;
2084 a0 += (a1 % 10000) << 16;
2089 a1 += (a2 % 1000) << 16;
2091 a0 += (a1 % 1000) << 16;
2093 r += (a0 % 1000) * 10000;
2096 /* If a was negative, replace a by (-1-a) and r by (9999999 - r) */
2099 /* Set a to -a - 1 (a is a2/a1/a0) */
2107 if (remainder) *remainder = r;
2109 /* Do not replace this by << 32, it gives a compiler warning and it does
2111 return ((((time_t)a2) << 16) << 16) + (a1 << 16) + a0;
2116 /***********************************************************************
2117 * MulDiv (KERNEL32.@)
2119 * Result of multiplication and division
2120 * -1: Overflow occurred or Divisor was 0
2127 #if SIZEOF_LONG_LONG >= 8
2130 if (!nDivisor) return -1;
2132 /* We want to deal with a positive divisor to simplify the logic. */
2135 nMultiplicand = - nMultiplicand;
2136 nDivisor = -nDivisor;
2139 /* If the result is positive, we "add" to round. else, we subtract to round. */
2140 if ( ( (nMultiplicand < 0) && (nMultiplier < 0) ) ||
2141 ( (nMultiplicand >= 0) && (nMultiplier >= 0) ) )
2142 ret = (((long long)nMultiplicand * nMultiplier) + (nDivisor/2)) / nDivisor;
2144 ret = (((long long)nMultiplicand * nMultiplier) - (nDivisor/2)) / nDivisor;
2146 if ((ret > 2147483647) || (ret < -2147483647)) return -1;
2149 if (!nDivisor) return -1;
2151 /* We want to deal with a positive divisor to simplify the logic. */
2154 nMultiplicand = - nMultiplicand;
2155 nDivisor = -nDivisor;
2158 /* If the result is positive, we "add" to round. else, we subtract to round. */
2159 if ( ( (nMultiplicand < 0) && (nMultiplier < 0) ) ||
2160 ( (nMultiplicand >= 0) && (nMultiplier >= 0) ) )
2161 return ((nMultiplicand * nMultiplier) + (nDivisor/2)) / nDivisor;
2163 return ((nMultiplicand * nMultiplier) - (nDivisor/2)) / nDivisor;
2169 /***********************************************************************
2170 * DosDateTimeToFileTime (KERNEL32.@)
2172 BOOL WINAPI DosDateTimeToFileTime( WORD fatdate, WORD fattime, LPFILETIME ft)
2177 time_t time1, time2;
2180 newtm.tm_sec = (fattime & 0x1f) * 2;
2181 newtm.tm_min = (fattime >> 5) & 0x3f;
2182 newtm.tm_hour = (fattime >> 11);
2183 newtm.tm_mday = (fatdate & 0x1f);
2184 newtm.tm_mon = ((fatdate >> 5) & 0x0f) - 1;
2185 newtm.tm_year = (fatdate >> 9) + 80;
2187 RtlSecondsSince1970ToTime( timegm(&newtm), ft );
2189 time1 = mktime(&newtm);
2190 gtm = gmtime(&time1);
2191 time2 = mktime(gtm);
2192 RtlSecondsSince1970ToTime( 2*time1-time2, ft );
2198 /***********************************************************************
2199 * FileTimeToDosDateTime (KERNEL32.@)
2201 BOOL WINAPI FileTimeToDosDateTime( const FILETIME *ft, LPWORD fatdate,
2204 time_t unixtime = DOSFS_FileTimeToUnixTime( ft, NULL );
2205 struct tm *tm = gmtime( &unixtime );
2207 *fattime = (tm->tm_hour << 11) + (tm->tm_min << 5) + (tm->tm_sec / 2);
2209 *fatdate = ((tm->tm_year - 80) << 9) + ((tm->tm_mon + 1) << 5)
2215 /***********************************************************************
2216 * LocalFileTimeToFileTime (KERNEL32.@)
2218 BOOL WINAPI LocalFileTimeToFileTime( const FILETIME *localft,
2225 /* Converts from local to UTC. */
2226 time_t localtime = DOSFS_FileTimeToUnixTime( localft, &remainder );
2227 xtm = gmtime( &localtime );
2228 utctime = mktime(xtm);
2229 if(xtm->tm_isdst > 0) utctime-=3600;
2230 DOSFS_UnixTimeToFileTime( utctime, utcft, remainder );
2235 /***********************************************************************
2236 * FileTimeToLocalFileTime (KERNEL32.@)
2238 BOOL WINAPI FileTimeToLocalFileTime( const FILETIME *utcft,
2239 LPFILETIME localft )
2242 /* Converts from UTC to local. */
2243 time_t unixtime = DOSFS_FileTimeToUnixTime( utcft, &remainder );
2245 struct tm *xtm = localtime( &unixtime );
2248 localtime = timegm(xtm);
2249 DOSFS_UnixTimeToFileTime( localtime, localft, remainder );
2255 xtm = gmtime( &unixtime );
2257 if(xtm->tm_isdst > 0) time-=3600;
2258 DOSFS_UnixTimeToFileTime( 2*unixtime-time, localft, remainder );
2264 /***********************************************************************
2265 * FileTimeToSystemTime (KERNEL32.@)
2267 BOOL WINAPI FileTimeToSystemTime( const FILETIME *ft, LPSYSTEMTIME syst )
2271 time_t xtime = DOSFS_FileTimeToUnixTime( ft, &remainder );
2272 xtm = gmtime(&xtime);
2273 syst->wYear = xtm->tm_year+1900;
2274 syst->wMonth = xtm->tm_mon + 1;
2275 syst->wDayOfWeek = xtm->tm_wday;
2276 syst->wDay = xtm->tm_mday;
2277 syst->wHour = xtm->tm_hour;
2278 syst->wMinute = xtm->tm_min;
2279 syst->wSecond = xtm->tm_sec;
2280 syst->wMilliseconds = remainder / 10000;
2284 /***********************************************************************
2285 * QueryDosDeviceA (KERNEL32.@)
2287 * returns array of strings terminated by \0, terminated by \0
2289 DWORD WINAPI QueryDosDeviceA(LPCSTR devname,LPSTR target,DWORD bufsize)
2294 TRACE("(%s,...)\n", devname ? devname : "<null>");
2296 /* return known MSDOS devices */
2297 static const char devices[24] = "CON\0COM1\0COM2\0LPT1\0NUL\0\0";
2298 memcpy( target, devices, min(bufsize,sizeof(devices)) );
2299 return min(bufsize,sizeof(devices));
2301 /* In theory all that are possible and have been defined.
2302 * Now just those below, since mirc uses it to check for special files.
2304 * (It is more complex, and supports netmounted stuff, and \\.\ stuff,
2305 * but currently we just ignore that.)
2307 #define CHECK(x) (strstr(devname,#x)==devname)
2308 if (CHECK(con) || CHECK(com) || CHECK(lpt) || CHECK(nul)) {
2309 strcpy(buffer,"\\DEV\\");
2310 strcat(buffer,devname);
2311 if ((s=strchr(buffer,':'))) *s='\0';
2312 lstrcpynA(target,buffer,bufsize);
2313 return strlen(buffer)+1;
2315 if (strchr(devname,':') || devname[0]=='\\') {
2316 /* This might be a DOS device we do not handle yet ... */
2317 FIXME("(%s) not detected as DOS device!\n",devname);
2319 SetLastError(ERROR_DEV_NOT_EXIST);
2326 /***********************************************************************
2327 * QueryDosDeviceW (KERNEL32.@)
2329 * returns array of strings terminated by \0, terminated by \0
2331 DWORD WINAPI QueryDosDeviceW(LPCWSTR devname,LPWSTR target,DWORD bufsize)
2333 LPSTR devnameA = devname?HEAP_strdupWtoA(GetProcessHeap(),0,devname):NULL;
2334 LPSTR targetA = (LPSTR)HeapAlloc(GetProcessHeap(),0,bufsize);
2335 DWORD ret = QueryDosDeviceA(devnameA,targetA,bufsize);
2337 ret = MultiByteToWideChar( CP_ACP, 0, targetA, ret, target, bufsize );
2338 if (devnameA) HeapFree(GetProcessHeap(),0,devnameA);
2339 if (targetA) HeapFree(GetProcessHeap(),0,targetA);
2344 /***********************************************************************
2345 * SystemTimeToFileTime (KERNEL32.@)
2347 BOOL WINAPI SystemTimeToFileTime( const SYSTEMTIME *syst, LPFILETIME ft )
2353 struct tm xtm,*utc_tm;
2354 time_t localtim,utctime;
2357 xtm.tm_year = syst->wYear-1900;
2358 xtm.tm_mon = syst->wMonth - 1;
2359 xtm.tm_wday = syst->wDayOfWeek;
2360 xtm.tm_mday = syst->wDay;
2361 xtm.tm_hour = syst->wHour;
2362 xtm.tm_min = syst->wMinute;
2363 xtm.tm_sec = syst->wSecond; /* this is UTC */
2366 utctime = timegm(&xtm);
2367 DOSFS_UnixTimeToFileTime( utctime, ft,
2368 syst->wMilliseconds * 10000 );
2370 localtim = mktime(&xtm); /* now we've got local time */
2371 utc_tm = gmtime(&localtim);
2372 utctime = mktime(utc_tm);
2373 DOSFS_UnixTimeToFileTime( 2*localtim -utctime, ft,
2374 syst->wMilliseconds * 10000 );
2379 /***********************************************************************
2380 * DefineDosDeviceA (KERNEL32.@)
2382 BOOL WINAPI DefineDosDeviceA(DWORD flags,LPCSTR devname,LPCSTR targetpath) {
2383 FIXME("(0x%08lx,%s,%s),stub!\n",flags,devname,targetpath);
2384 SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
2389 --- 16 bit functions ---
2392 /*************************************************************************
2393 * FindFirstFile (KERNEL.413)
2395 HANDLE16 WINAPI FindFirstFile16( LPCSTR path, WIN32_FIND_DATAA *data )
2397 DOS_FULL_NAME full_name;
2399 FIND_FIRST_INFO *info;
2401 data->dwReserved0 = data->dwReserved1 = 0x0;
2402 if (!path) return 0;
2403 if (!DOSFS_GetFullName( path, FALSE, &full_name ))
2404 return INVALID_HANDLE_VALUE16;
2405 if (!(handle = GlobalAlloc16( GMEM_MOVEABLE, sizeof(FIND_FIRST_INFO) )))
2406 return INVALID_HANDLE_VALUE16;
2407 info = (FIND_FIRST_INFO *)GlobalLock16( handle );
2408 info->path = HeapAlloc( GetProcessHeap(), 0, strlen(full_name.long_name)+1 );
2409 strcpy( info->path, full_name.long_name );
2410 info->long_mask = strrchr( info->path, '/' );
2411 if (info->long_mask )
2412 *(info->long_mask++) = '\0';
2413 info->short_mask = NULL;
2415 if (path[0] && (path[1] == ':')) info->drive = FILE_toupper(*path) - 'A';
2416 else info->drive = DRIVE_GetCurrentDrive();
2419 info->u.dos_dir = DOSFS_OpenDir( info->path );
2421 GlobalUnlock16( handle );
2422 if (!FindNextFile16( handle, data ))
2424 FindClose16( handle );
2425 SetLastError( ERROR_NO_MORE_FILES );
2426 return INVALID_HANDLE_VALUE16;
2431 /*************************************************************************
2432 * FindNextFile (KERNEL.414)
2434 BOOL16 WINAPI FindNextFile16( HANDLE16 handle, WIN32_FIND_DATAA *data )
2436 FIND_FIRST_INFO *info;
2438 if ((handle == INVALID_HANDLE_VALUE16) ||
2439 !(info = (FIND_FIRST_INFO *)GlobalLock16( handle )))
2441 SetLastError( ERROR_INVALID_HANDLE );
2444 GlobalUnlock16( handle );
2445 if (!info->path || !info->u.dos_dir)
2447 SetLastError( ERROR_NO_MORE_FILES );
2450 if (!DOSFS_FindNextEx( info, data ))
2452 DOSFS_CloseDir( info->u.dos_dir ); info->u.dos_dir = NULL;
2453 HeapFree( GetProcessHeap(), 0, info->path );
2454 info->path = info->long_mask = NULL;
2455 SetLastError( ERROR_NO_MORE_FILES );
2461 /*************************************************************************
2462 * FindClose (KERNEL.415)
2464 BOOL16 WINAPI FindClose16( HANDLE16 handle )
2466 FIND_FIRST_INFO *info;
2468 if ((handle == INVALID_HANDLE_VALUE16) ||
2469 !(info = (FIND_FIRST_INFO *)GlobalLock16( handle )))
2471 SetLastError( ERROR_INVALID_HANDLE );
2474 if (info->u.dos_dir) DOSFS_CloseDir( info->u.dos_dir );
2475 if (info->path) HeapFree( GetProcessHeap(), 0, info->path );
2476 GlobalUnlock16( handle );
2477 GlobalFree16( handle );