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>
36 #ifdef HAVE_SYS_IOCTL_H
37 #include <sys/ioctl.h>
44 #define NONAMELESSUNION
45 #define NONAMELESSSTRUCT
52 #include "wine/unicode.h"
53 #include "wine/winbase16.h"
58 #include "wine/server.h"
63 #include "wine/debug.h"
65 WINE_DEFAULT_DEBUG_CHANNEL(dosfs
);
66 WINE_DECLARE_DEBUG_CHANNEL(file
);
68 /* Define the VFAT ioctl to get both short and long file names */
69 /* FIXME: is it possible to get this to work on other systems? */
71 /* We want the real kernel dirent structure, not the libc one */
76 unsigned short d_reclen
;
80 #define VFAT_IOCTL_READDIR_BOTH _IOR('r', 1, KERNEL_DIRENT [2] )
82 /* To avoid blocking on non-directories in DOSFS_OpenDir_VFAT*/
84 # define O_DIRECTORY 0200000 /* must be directory */
88 #undef VFAT_IOCTL_READDIR_BOTH /* just in case... */
91 #define IS_OPTION_TRUE(ch) ((ch) == 'y' || (ch) == 'Y' || (ch) == 't' || (ch) == 'T' || (ch) == '1')
93 /* Chars we don't want to see in DOS file names */
94 #define INVALID_DOS_CHARS "*?<>|\"+=,;[] \345"
96 static const DOS_DEVICE DOSFS_Devices
[] =
97 /* name, device flags (see Int 21/AX=0x4400) */
99 { {'C','O','N',0}, 0xc0d3 },
100 { {'P','R','N',0}, 0xa0c0 },
101 { {'N','U','L',0}, 0x80c4 },
102 { {'A','U','X',0}, 0x80c0 },
103 { {'L','P','T','1',0}, 0xa0c0 },
104 { {'L','P','T','2',0}, 0xa0c0 },
105 { {'L','P','T','3',0}, 0xa0c0 },
106 { {'L','P','T','4',0}, 0xc0d3 },
107 { {'C','O','M','1',0}, 0x80c0 },
108 { {'C','O','M','2',0}, 0x80c0 },
109 { {'C','O','M','3',0}, 0x80c0 },
110 { {'C','O','M','4',0}, 0x80c0 },
111 { {'S','C','S','I','M','G','R','$',0}, 0xc0c0 },
112 { {'H','P','S','C','A','N',0}, 0xc0c0 },
113 { {'E','M','M','X','X','X','X','0',0}, 0x0000 }
116 static const WCHAR devW
[] = {'\\','D','e','v','i','c','e','\\',0};
117 static const WCHAR dosW
[] = {'\\','D','o','s','D','e','v','i','c','e','s','\\',0};
119 static const WCHAR auxW
[] = {'A','U','X',0};
120 static const WCHAR comW
[] = {'C','O','M',0};
121 static const WCHAR lptW
[] = {'L','P','T',0};
122 static const WCHAR nulW
[] = {'N','U','L',0};
124 static const WCHAR nullW
[] = {'N','u','l','l',0};
125 static const WCHAR parW
[] = {'P','a','r','a','l','l','e','l',0};
126 static const WCHAR serW
[] = {'S','e','r','i','a','l',0};
127 static const WCHAR oneW
[] = {'1',0};
130 * Directory info for DOSFS_ReadDir
131 * contains the names of *all* the files in the directory
140 /* Info structure for FindFirstFile handle */
143 char *path
; /* unix path */
156 static WINE_EXCEPTION_FILTER(page_fault
)
158 if (GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION
)
159 return EXCEPTION_EXECUTE_HANDLER
;
160 return EXCEPTION_CONTINUE_SEARCH
;
164 /***********************************************************************
167 * Return 1 if Unix file 'name' is also a valid MS-DOS name
168 * (i.e. contains only valid DOS chars, lower-case only, fits in 8.3 format).
169 * File name can be terminated by '\0', '\\' or '/'.
171 static int DOSFS_ValidDOSName( LPCWSTR name
, int ignore_case
)
173 static const char invalid_chars
[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" INVALID_DOS_CHARS
;
174 const WCHAR
*p
= name
;
175 const char *invalid
= ignore_case
? (invalid_chars
+ 26) : invalid_chars
;
180 /* Check for "." and ".." */
183 /* All other names beginning with '.' are invalid */
184 return (IS_END_OF_NAME(*p
));
186 while (!IS_END_OF_NAME(*p
))
188 if (*p
< 256 && strchr( invalid
, (char)*p
)) return 0; /* Invalid char */
189 if (*p
== '.') break; /* Start of the extension */
190 if (++len
> 8) return 0; /* Name too long */
193 if (*p
!= '.') return 1; /* End of name */
195 if (IS_END_OF_NAME(*p
)) return 0; /* Empty extension not allowed */
197 while (!IS_END_OF_NAME(*p
))
199 if (*p
< 256 && strchr( invalid
, (char)*p
)) return 0; /* Invalid char */
200 if (*p
== '.') return 0; /* Second extension not allowed */
201 if (++len
> 3) return 0; /* Extension too long */
208 /***********************************************************************
209 * DOSFS_ToDosFCBFormat
211 * Convert a file name to DOS FCB format (8+3 chars, padded with blanks),
212 * expanding wild cards and converting to upper-case in the process.
213 * File name can be terminated by '\0', '\\' or '/'.
214 * Return FALSE if the name is not a valid DOS name.
215 * 'buffer' must be at least 12 characters long.
217 BOOL
DOSFS_ToDosFCBFormat( LPCWSTR name
, LPWSTR buffer
)
219 static const char invalid_chars
[] = INVALID_DOS_CHARS
;
223 /* Check for "." and ".." */
228 for(i
= 1; i
< 11; i
++) buffer
[i
] = ' ';
235 return (!*p
|| (*p
== '/') || (*p
== '\\'));
238 for (i
= 0; i
< 8; i
++)
255 if (*p
< 256 && strchr( invalid_chars
, (char)*p
)) return FALSE
;
256 buffer
[i
] = toupperW(*p
);
264 /* Skip all chars after wildcard up to first dot */
265 while (*p
&& (*p
!= '/') && (*p
!= '\\') && (*p
!= '.')) p
++;
269 /* Check if name too long */
270 if (*p
&& (*p
!= '/') && (*p
!= '\\') && (*p
!= '.')) return FALSE
;
272 if (*p
== '.') p
++; /* Skip dot */
274 for (i
= 8; i
< 11; i
++)
284 return FALSE
; /* Second extension not allowed */
292 if (*p
< 256 && strchr( invalid_chars
, (char)*p
)) return FALSE
;
293 buffer
[i
] = toupperW(*p
);
300 /* at most 3 character of the extension are processed
301 * is something behind this ?
303 while (*p
== '*' || *p
== ' ') p
++; /* skip wildcards and spaces */
304 return IS_END_OF_NAME(*p
);
308 /***********************************************************************
309 * DOSFS_ToDosDTAFormat
311 * Convert a file name from FCB to DTA format (name.ext, null-terminated)
312 * converting to upper-case in the process.
313 * File name can be terminated by '\0', '\\' or '/'.
314 * 'buffer' must be at least 13 characters long.
316 static void DOSFS_ToDosDTAFormat( LPCWSTR name
, LPWSTR buffer
)
320 memcpy( buffer
, name
, 8 * sizeof(WCHAR
) );
322 while ((p
> buffer
) && (p
[-1] == ' ')) p
--;
324 memcpy( p
, name
+ 8, 3 * sizeof(WCHAR
) );
326 while (p
[-1] == ' ') p
--;
327 if (p
[-1] == '.') p
--;
332 /***********************************************************************
335 * Check a long file name against a mask.
337 * Tests (done in W95 DOS shell - case insensitive):
338 * *.txt test1.test.txt *
340 * *.t??????.t* test1.ta.tornado.txt *
341 * *tornado* test1.ta.tornado.txt *
342 * t*t test1.ta.tornado.txt *
344 * ?est??? test1.txt -
345 * *test1.txt* test1.txt *
346 * h?l?o*t.dat hellothisisatest.dat *
348 static int DOSFS_MatchLong( LPCWSTR mask
, LPCWSTR name
, int case_sensitive
)
350 LPCWSTR lastjoker
= NULL
;
351 LPCWSTR next_to_retry
= NULL
;
352 static const WCHAR asterisk_dot_asterisk
[] = {'*','.','*',0};
354 TRACE("(%s, %s, %x)\n", debugstr_w(mask
), debugstr_w(name
), case_sensitive
);
356 if (!strcmpW( mask
, asterisk_dot_asterisk
)) return 1;
357 while (*name
&& *mask
)
362 while (*mask
== '*') mask
++; /* Skip consecutive '*' */
364 if (!*mask
) return 1; /* end of mask is all '*', so match */
366 /* skip to the next match after the joker(s) */
367 if (case_sensitive
) while (*name
&& (*name
!= *mask
)) name
++;
368 else while (*name
&& (toupperW(*name
) != toupperW(*mask
))) name
++;
371 next_to_retry
= name
;
373 else if (*mask
!= '?')
378 if (*mask
!= *name
) mismatch
= 1;
382 if (toupperW(*mask
) != toupperW(*name
)) mismatch
= 1;
396 else /* mismatch ! */
398 if (lastjoker
) /* we had an '*', so we can try unlimitedly */
402 /* this scan sequence was a mismatch, so restart
403 * 1 char after the first char we checked last time */
405 name
= next_to_retry
;
408 return 0; /* bad luck */
417 while ((*mask
== '.') || (*mask
== '*'))
418 mask
++; /* Ignore trailing '.' or '*' in mask */
419 return (!*name
&& !*mask
);
423 /***********************************************************************
426 * Used to construct an array of filenames in DOSFS_OpenDir
428 static BOOL
DOSFS_AddDirEntry(DOS_DIR
**dir
, LPCWSTR name
, LPCWSTR dosname
)
430 int extra1
= strlenW(name
) + 1;
431 int extra2
= strlenW(dosname
) + 1;
433 /* if we need more, at minimum double the size */
434 if( (extra1
+ extra2
+ (*dir
)->used
) > (*dir
)->size
)
436 int more
= (*dir
)->size
;
439 if(more
<(extra1
+extra2
))
440 more
= extra1
+extra2
;
442 t
= HeapReAlloc(GetProcessHeap(), 0, *dir
, sizeof(**dir
) +
443 ((*dir
)->size
+ more
)*sizeof(WCHAR
) );
446 SetLastError( ERROR_NOT_ENOUGH_MEMORY
);
447 ERR("Out of memory caching directory structure %d %d %d\n",
448 (*dir
)->size
, more
, (*dir
)->used
);
452 (*dir
)->size
+= more
;
455 /* at this point, the dir structure is big enough to hold these names */
456 strcpyW(&(*dir
)->names
[(*dir
)->used
], name
);
457 (*dir
)->used
+= extra1
;
458 strcpyW(&(*dir
)->names
[(*dir
)->used
], dosname
);
459 (*dir
)->used
+= extra2
;
465 /***********************************************************************
468 static BOOL
DOSFS_OpenDir_VFAT(DOS_DIR
**dir
, const char *unix_path
)
470 #ifdef VFAT_IOCTL_READDIR_BOTH
472 int fd
= open( unix_path
, O_RDONLY
|O_DIRECTORY
);
475 /* Check if the VFAT ioctl is supported on this directory */
482 WCHAR long_name
[MAX_PATH
];
483 WCHAR short_name
[12];
485 r
= (ioctl( fd
, VFAT_IOCTL_READDIR_BOTH
, (long)de
) != -1);
490 MultiByteToWideChar(CP_UNIXCP
, 0, de
[0].d_name
, -1, long_name
, MAX_PATH
);
491 if (!DOSFS_ToDosFCBFormat( long_name
, short_name
))
492 short_name
[0] = '\0';
494 MultiByteToWideChar(CP_UNIXCP
, 0, de
[1].d_name
, -1, long_name
, MAX_PATH
);
496 MultiByteToWideChar(CP_UNIXCP
, 0, de
[0].d_name
, -1, long_name
, MAX_PATH
);
497 r
= DOSFS_AddDirEntry(dir
, long_name
, short_name
);
503 static const WCHAR empty_strW
[] = { 0 };
504 DOSFS_AddDirEntry(dir
, empty_strW
, empty_strW
);
510 #endif /* VFAT_IOCTL_READDIR_BOTH */
514 /***********************************************************************
515 * DOSFS_OpenDir_Normal
517 * Now use the standard opendir/readdir interface
519 static BOOL
DOSFS_OpenDir_Normal( DOS_DIR
**dir
, const char *unix_path
)
521 DIR *unixdir
= opendir( unix_path
);
523 static const WCHAR empty_strW
[] = { 0 };
529 WCHAR long_name
[MAX_PATH
];
530 struct dirent
*de
= readdir(unixdir
);
534 MultiByteToWideChar(CP_UNIXCP
, 0, de
->d_name
, -1, long_name
, MAX_PATH
);
535 r
= DOSFS_AddDirEntry(dir
, long_name
, empty_strW
);
540 DOSFS_AddDirEntry(dir
, empty_strW
, empty_strW
);
545 /***********************************************************************
548 static DOS_DIR
*DOSFS_OpenDir( const char *unix_path
)
550 const int init_size
= 0x100;
551 DOS_DIR
*dir
= HeapAlloc( GetProcessHeap(), 0, sizeof(*dir
) + init_size
*sizeof (WCHAR
));
554 TRACE("%s\n",debugstr_a(unix_path
));
558 SetLastError( ERROR_NOT_ENOUGH_MEMORY
);
562 dir
->size
= init_size
;
564 /* Treat empty path as root directory. This simplifies path split into
565 directory and mask in several other places */
566 if (!*unix_path
) unix_path
= "/";
568 r
= DOSFS_OpenDir_VFAT( &dir
, unix_path
);
571 r
= DOSFS_OpenDir_Normal( &dir
, unix_path
);
575 HeapFree(GetProcessHeap(), 0, dir
);
584 /***********************************************************************
587 static void DOSFS_CloseDir( DOS_DIR
*dir
)
589 HeapFree( GetProcessHeap(), 0, dir
);
593 /***********************************************************************
596 static BOOL
DOSFS_ReadDir( DOS_DIR
*dir
, LPCWSTR
*long_name
,
597 LPCWSTR
*short_name
)
604 /* the long pathname is first */
605 ln
= &dir
->names
[dir
->used
];
610 dir
->used
+= (strlenW(ln
) + 1);
612 /* followed by the short path name */
613 sn
= &dir
->names
[dir
->used
];
618 dir
->used
+= (strlenW(sn
) + 1);
624 /***********************************************************************
627 * Transform a Unix file name into a hashed DOS name. If the name is a valid
628 * DOS name, it is converted to upper-case; otherwise it is replaced by a
629 * hashed version that fits in 8.3 format.
630 * File name can be terminated by '\0', '\\' or '/'.
631 * 'buffer' must be at least 13 characters long.
633 static void DOSFS_Hash( LPCWSTR name
, LPWSTR buffer
, BOOL dir_format
,
636 static const char invalid_chars
[] = INVALID_DOS_CHARS
"~.";
637 static const char hash_chars
[32] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345";
646 for(i
= 0; i
< 11; i
++) buffer
[i
] = ' ';
650 if (DOSFS_ValidDOSName( name
, ignore_case
))
652 /* Check for '.' and '..' */
656 if (!dir_format
) buffer
[1] = buffer
[2] = '\0';
657 if (name
[1] == '.') buffer
[1] = '.';
661 /* Simply copy the name, converting to uppercase */
663 for (dst
= buffer
; !IS_END_OF_NAME(*name
) && (*name
!= '.'); name
++)
664 *dst
++ = toupperW(*name
);
667 if (dir_format
) dst
= buffer
+ 8;
669 for (name
++; !IS_END_OF_NAME(*name
); name
++)
670 *dst
++ = toupperW(*name
);
672 if (!dir_format
) *dst
= '\0';
676 /* Compute the hash code of the file name */
677 /* If you know something about hash functions, feel free to */
678 /* insert a better algorithm here... */
681 for (p
= name
, hash
= 0xbeef; !IS_END_OF_NAME(p
[1]); p
++)
682 hash
= (hash
<<3) ^ (hash
>>5) ^ tolowerW(*p
) ^ (tolowerW(p
[1]) << 8);
683 hash
= (hash
<<3) ^ (hash
>>5) ^ tolowerW(*p
); /* Last character */
687 for (p
= name
, hash
= 0xbeef; !IS_END_OF_NAME(p
[1]); p
++)
688 hash
= (hash
<< 3) ^ (hash
>> 5) ^ *p
^ (p
[1] << 8);
689 hash
= (hash
<< 3) ^ (hash
>> 5) ^ *p
; /* Last character */
692 /* Find last dot for start of the extension */
693 for (p
= name
+1, ext
= NULL
; !IS_END_OF_NAME(*p
); p
++)
694 if (*p
== '.') ext
= p
;
695 if (ext
&& IS_END_OF_NAME(ext
[1]))
696 ext
= NULL
; /* Empty extension ignored */
698 /* Copy first 4 chars, replacing invalid chars with '_' */
699 for (i
= 4, p
= name
, dst
= buffer
; i
> 0; i
--, p
++)
701 if (IS_END_OF_NAME(*p
) || (p
== ext
)) break;
702 *dst
++ = (*p
< 256 && strchr( invalid_chars
, (char)*p
)) ? '_' : toupperW(*p
);
704 /* Pad to 5 chars with '~' */
705 while (i
-- >= 0) *dst
++ = '~';
707 /* Insert hash code converted to 3 ASCII chars */
708 *dst
++ = hash_chars
[(hash
>> 10) & 0x1f];
709 *dst
++ = hash_chars
[(hash
>> 5) & 0x1f];
710 *dst
++ = hash_chars
[hash
& 0x1f];
712 /* Copy the first 3 chars of the extension (if any) */
715 if (!dir_format
) *dst
++ = '.';
716 for (i
= 3, ext
++; (i
> 0) && !IS_END_OF_NAME(*ext
); i
--, ext
++)
717 *dst
++ = (*ext
< 256 && strchr( invalid_chars
, (char)*ext
)) ? '_' : toupperW(*ext
);
719 if (!dir_format
) *dst
= '\0';
723 /***********************************************************************
726 * Find the Unix file name in a given directory that corresponds to
727 * a file name (either in Unix or DOS format).
728 * File name can be terminated by '\0', '\\' or '/'.
729 * Return TRUE if OK, FALSE if no file name matches.
731 * 'long_buf' must be at least 'long_len' characters long. If the long name
732 * turns out to be larger than that, the function returns FALSE.
733 * 'short_buf' must be at least 13 characters long.
735 BOOL
DOSFS_FindUnixName( const DOS_FULL_NAME
*path
, LPCWSTR name
, char *long_buf
,
736 INT long_len
, LPWSTR short_buf
, BOOL ignore_case
)
739 LPCWSTR long_name
, short_name
;
740 WCHAR dos_name
[12], tmp_buf
[13];
743 LPCWSTR p
= strchrW( name
, '/' );
744 int len
= p
? (int)(p
- name
) : strlenW(name
);
745 if ((p
= strchrW( name
, '\\' ))) len
= min( (int)(p
- name
), len
);
746 /* Ignore trailing dots and spaces */
747 while (len
> 1 && (name
[len
-1] == '.' || name
[len
-1] == ' ')) len
--;
748 if (long_len
< len
+ 1) return FALSE
;
750 TRACE("%s,%s\n", path
->long_name
, debugstr_w(name
) );
752 if (!DOSFS_ToDosFCBFormat( name
, dos_name
)) dos_name
[0] = '\0';
754 if (!(dir
= DOSFS_OpenDir( path
->long_name
)))
756 WARN("(%s,%s): can't open dir: %s\n",
757 path
->long_name
, debugstr_w(name
), strerror(errno
) );
761 while ((ret
= DOSFS_ReadDir( dir
, &long_name
, &short_name
)))
763 /* Check against Unix name */
764 if (len
== strlenW(long_name
))
768 if (!strncmpW( long_name
, name
, len
)) break;
772 if (!strncmpiW( long_name
, name
, len
)) break;
777 /* Check against hashed DOS name */
780 DOSFS_Hash( long_name
, tmp_buf
, TRUE
, ignore_case
);
781 short_name
= tmp_buf
;
783 if (!strcmpW( dos_name
, short_name
)) break;
788 if (long_buf
) WideCharToMultiByte(CP_UNIXCP
, 0, long_name
, -1, long_buf
, long_len
, NULL
, NULL
);
792 DOSFS_ToDosDTAFormat( short_name
, short_buf
);
794 DOSFS_Hash( long_name
, short_buf
, FALSE
, ignore_case
);
796 TRACE("(%s,%s) -> %s (%s)\n", path
->long_name
, debugstr_w(name
),
797 debugstr_w(long_name
), short_buf
? debugstr_w(short_buf
) : "***");
800 WARN("%s not found in '%s'\n", debugstr_w(name
), path
->long_name
);
801 DOSFS_CloseDir( dir
);
806 /***********************************************************************
809 * Check if a DOS file name represents a DOS device and return the device.
811 const DOS_DEVICE
*DOSFS_GetDevice( LPCWSTR name
)
816 if (!name
) return NULL
; /* if wine_server_handle_to_fd was used */
817 if (name
[0] && (name
[1] == ':')) name
+= 2;
818 if ((p
= strrchrW( name
, '/' ))) name
= p
+ 1;
819 if ((p
= strrchrW( name
, '\\' ))) name
= p
+ 1;
820 for (i
= 0; i
< sizeof(DOSFS_Devices
)/sizeof(DOSFS_Devices
[0]); i
++)
822 const WCHAR
*dev
= DOSFS_Devices
[i
].name
;
823 if (!strncmpiW( dev
, name
, strlenW(dev
) ))
825 p
= name
+ strlenW( dev
);
826 if (!*p
|| (*p
== '.') || (*p
== ':')) return &DOSFS_Devices
[i
];
833 /***********************************************************************
834 * DOSFS_GetDeviceByHandle
836 const DOS_DEVICE
*DOSFS_GetDeviceByHandle( HANDLE hFile
)
838 const DOS_DEVICE
*ret
= NULL
;
839 SERVER_START_REQ( get_device_id
)
842 if (!wine_server_call( req
))
844 if ((reply
->id
>= 0) &&
845 (reply
->id
< sizeof(DOSFS_Devices
)/sizeof(DOSFS_Devices
[0])))
846 ret
= &DOSFS_Devices
[reply
->id
];
854 /**************************************************************************
855 * DOSFS_CreateCommPort
857 static HANDLE
DOSFS_CreateCommPort(LPCWSTR name
, DWORD access
, DWORD attributes
, LPSECURITY_ATTRIBUTES sa
)
862 OBJECT_ATTRIBUTES attr
;
863 UNICODE_STRING nameW
;
868 static const WCHAR serialportsW
[] = {'M','a','c','h','i','n','e','\\',
869 'S','o','f','t','w','a','r','e','\\',
870 'W','i','n','e','\\','W','i','n','e','\\',
871 'C','o','n','f','i','g','\\',
872 'S','e','r','i','a','l','P','o','r','t','s',0};
874 TRACE_(file
)("%s %lx %lx\n", debugstr_w(name
), access
, attributes
);
876 attr
.Length
= sizeof(attr
);
877 attr
.RootDirectory
= 0;
878 attr
.ObjectName
= &nameW
;
880 attr
.SecurityDescriptor
= NULL
;
881 attr
.SecurityQualityOfService
= NULL
;
882 RtlInitUnicodeString( &nameW
, serialportsW
);
884 if (NtOpenKey( &hkey
, KEY_ALL_ACCESS
, &attr
)) return 0;
886 RtlInitUnicodeString( &nameW
, name
);
887 if (!NtQueryValueKey( hkey
, &nameW
, KeyValuePartialInformation
, tmp
, sizeof(tmp
), &dummy
))
888 devnameW
= (WCHAR
*)((KEY_VALUE_PARTIAL_INFORMATION
*)tmp
)->Data
;
894 if (!devnameW
) return 0;
895 WideCharToMultiByte(CP_ACP
, 0, devnameW
, -1, devname
, sizeof(devname
), NULL
, NULL
);
897 TRACE("opening %s as %s\n", devname
, debugstr_w(name
));
899 SERVER_START_REQ( create_serial
)
901 req
->access
= access
;
902 req
->inherit
= (sa
&& (sa
->nLength
>=sizeof(*sa
)) && sa
->bInheritHandle
);
903 req
->attributes
= attributes
;
904 req
->sharing
= FILE_SHARE_READ
|FILE_SHARE_WRITE
;
905 wine_server_add_data( req
, devname
, strlen(devname
) );
907 wine_server_call_err( req
);
913 ERR("Couldn't open device '%s' ! (check permissions)\n",devname
);
915 TRACE("return %p\n", ret
);
919 /***********************************************************************
922 * Open a DOS device. This might not map 1:1 into the UNIX device concept.
923 * Returns 0 on failure.
925 HANDLE
DOSFS_OpenDevice( LPCWSTR name
, DWORD access
, DWORD attributes
, LPSECURITY_ATTRIBUTES sa
)
931 if (name
[0] && (name
[1] == ':')) name
+= 2;
932 if ((p
= strrchrW( name
, '/' ))) name
= p
+ 1;
933 if ((p
= strrchrW( name
, '\\' ))) name
= p
+ 1;
934 for (i
= 0; i
< sizeof(DOSFS_Devices
)/sizeof(DOSFS_Devices
[0]); i
++)
936 const WCHAR
*dev
= DOSFS_Devices
[i
].name
;
937 if (!strncmpiW( dev
, name
, strlenW(dev
) ))
939 p
= name
+ strlenW( dev
);
940 if (!*p
|| (*p
== '.') || (*p
== ':')) {
941 static const WCHAR nulW
[] = {'N','U','L',0};
942 static const WCHAR conW
[] = {'C','O','N',0};
943 static const WCHAR scsimgrW
[] = {'S','C','S','I','M','G','R','$',0};
944 static const WCHAR hpscanW
[] = {'H','P','S','C','A','N',0};
945 static const WCHAR emmxxxx0W
[] = {'E','M','M','X','X','X','X','0',0};
947 if (!strcmpiW(DOSFS_Devices
[i
].name
, nulW
))
948 return FILE_CreateFile( "/dev/null", access
,
949 FILE_SHARE_READ
|FILE_SHARE_WRITE
, sa
,
950 OPEN_EXISTING
, 0, 0, TRUE
, DRIVE_UNKNOWN
);
951 if (!strcmpiW(DOSFS_Devices
[i
].name
, conW
)) {
953 switch (access
& (GENERIC_READ
|GENERIC_WRITE
)) {
955 to_dup
= GetStdHandle( STD_INPUT_HANDLE
);
958 to_dup
= GetStdHandle( STD_OUTPUT_HANDLE
);
961 FIXME("can't open CON read/write\n");
964 if (!DuplicateHandle( GetCurrentProcess(), to_dup
, GetCurrentProcess(),
966 sa
&& (sa
->nLength
>=sizeof(*sa
)) && sa
->bInheritHandle
,
967 DUPLICATE_SAME_ACCESS
))
971 if (!strcmpiW(DOSFS_Devices
[i
].name
, scsimgrW
) ||
972 !strcmpiW(DOSFS_Devices
[i
].name
, hpscanW
) ||
973 !strcmpiW(DOSFS_Devices
[i
].name
, emmxxxx0W
))
975 return FILE_CreateDevice( i
, access
, sa
);
978 if( (handle
=DOSFS_CreateCommPort(DOSFS_Devices
[i
].name
,access
,attributes
,sa
)) )
980 FIXME("device open %s not supported (yet)\n", debugstr_w(DOSFS_Devices
[i
].name
));
989 /***********************************************************************
992 * Get the drive specified by a given path name (DOS or Unix format).
994 static int DOSFS_GetPathDrive( LPCWSTR
*name
)
999 if (*p
&& (p
[1] == ':'))
1001 drive
= toupperW(*p
) - 'A';
1004 else if (*p
== '/') /* Absolute Unix path? */
1006 if ((drive
= DRIVE_FindDriveRootW( name
)) == -1)
1008 MESSAGE("Warning: %s not accessible from a configured DOS drive\n", debugstr_w(*name
) );
1009 /* Assume it really was a DOS name */
1010 drive
= DRIVE_GetCurrentDrive();
1013 else drive
= DRIVE_GetCurrentDrive();
1015 if (!DRIVE_IsValid(drive
))
1017 SetLastError( ERROR_INVALID_DRIVE
);
1024 /***********************************************************************
1027 * Convert a file name (DOS or mixed DOS/Unix format) to a valid
1028 * Unix name / short DOS name pair.
1029 * Return FALSE if one of the path components does not exist. The last path
1030 * component is only checked if 'check_last' is non-zero.
1031 * The buffers pointed to by 'long_buf' and 'short_buf' must be
1032 * at least MAX_PATHNAME_LEN long.
1034 BOOL
DOSFS_GetFullName( LPCWSTR name
, BOOL check_last
, DOS_FULL_NAME
*full
)
1040 static const WCHAR driveA_rootW
[] = {'A',':','\\',0};
1041 static const WCHAR dos_rootW
[] = {'\\',0};
1043 TRACE("%s (last=%d)\n", debugstr_w(name
), check_last
);
1045 if ((!*name
) || (*name
=='\n'))
1046 { /* error code for Win98 */
1047 SetLastError(ERROR_BAD_PATHNAME
);
1051 if ((full
->drive
= DOSFS_GetPathDrive( &name
)) == -1) return FALSE
;
1052 flags
= DRIVE_GetFlags( full
->drive
);
1054 lstrcpynA( full
->long_name
, DRIVE_GetRoot( full
->drive
),
1055 sizeof(full
->long_name
) );
1056 if (full
->long_name
[1]) root
= full
->long_name
+ strlen(full
->long_name
);
1057 else root
= full
->long_name
; /* root directory */
1059 strcpyW( full
->short_name
, driveA_rootW
);
1060 full
->short_name
[0] += full
->drive
;
1062 if ((*name
== '\\') || (*name
== '/')) /* Absolute path */
1064 while ((*name
== '\\') || (*name
== '/')) name
++;
1066 else /* Relative path */
1068 lstrcpynA( root
+ 1, DRIVE_GetUnixCwd( full
->drive
),
1069 sizeof(full
->long_name
) - (root
- full
->long_name
) - 1 );
1070 if (root
[1]) *root
= '/';
1071 lstrcpynW( full
->short_name
+ 3, DRIVE_GetDosCwd( full
->drive
),
1072 sizeof(full
->short_name
)/sizeof(full
->short_name
[0]) - 3 );
1075 p_l
= full
->long_name
[1] ? full
->long_name
+ strlen(full
->long_name
)
1077 p_s
= full
->short_name
[3] ? full
->short_name
+ strlenW(full
->short_name
)
1078 : full
->short_name
+ 2;
1081 while (*name
&& found
)
1083 /* Check for '.' and '..' */
1087 if (IS_END_OF_NAME(name
[1]))
1090 while ((*name
== '\\') || (*name
== '/')) name
++;
1093 else if ((name
[1] == '.') && IS_END_OF_NAME(name
[2]))
1096 while ((*name
== '\\') || (*name
== '/')) name
++;
1097 while ((p_l
> root
) && (*p_l
!= '/')) p_l
--;
1098 while ((p_s
> full
->short_name
+ 2) && (*p_s
!= '\\')) p_s
--;
1099 *p_l
= *p_s
= '\0'; /* Remove trailing separator */
1104 /* Make sure buffers are large enough */
1106 if ((p_s
>= full
->short_name
+ sizeof(full
->short_name
)/sizeof(full
->short_name
[0]) - 14) ||
1107 (p_l
>= full
->long_name
+ sizeof(full
->long_name
) - 1))
1109 SetLastError( ERROR_PATH_NOT_FOUND
);
1113 /* Get the long and short name matching the file name */
1115 if ((found
= DOSFS_FindUnixName( full
, name
, p_l
+ 1,
1116 sizeof(full
->long_name
) - (p_l
- full
->long_name
) - 1,
1117 p_s
+ 1, !(flags
& DRIVE_CASE_SENSITIVE
) )))
1122 p_s
+= strlenW(p_s
);
1123 while (!IS_END_OF_NAME(*name
)) name
++;
1125 else if (!check_last
)
1129 while (!IS_END_OF_NAME(*name
) &&
1130 (p_s
< full
->short_name
+ sizeof(full
->short_name
)/sizeof(full
->short_name
[0]) - 1) &&
1131 (p_l
< full
->long_name
+ sizeof(full
->long_name
) - 1))
1134 *p_s
++ = tolowerW(*name
);
1135 /* If the drive is case-sensitive we want to create new */
1136 /* files in lower-case otherwise we can't reopen them */
1137 /* under the same short name. */
1138 if (flags
& DRIVE_CASE_SENSITIVE
) wch
= tolowerW(*name
);
1140 p_l
+= WideCharToMultiByte(CP_UNIXCP
, 0, &wch
, 1, p_l
, 2, NULL
, NULL
);
1143 /* Ignore trailing dots and spaces */
1144 while(p_l
[-1] == '.' || p_l
[-1] == ' ') {
1151 while ((*name
== '\\') || (*name
== '/')) name
++;
1158 SetLastError( ERROR_FILE_NOT_FOUND
);
1161 if (*name
) /* Not last */
1163 SetLastError( ERROR_PATH_NOT_FOUND
);
1167 if (!full
->long_name
[0]) strcpy( full
->long_name
, "/" );
1168 if (!full
->short_name
[2]) strcpyW( full
->short_name
+ 2, dos_rootW
);
1169 TRACE("returning %s = %s\n", full
->long_name
, debugstr_w(full
->short_name
) );
1174 /***********************************************************************
1175 * GetShortPathNameW (KERNEL32.@)
1179 * longpath=NULL: LastError=ERROR_INVALID_PARAMETER, ret=0
1180 * longpath="" or invalid: LastError=ERROR_BAD_PATHNAME, ret=0
1182 * more observations ( with NT 3.51 (WinDD) ):
1183 * longpath <= 8.3 -> just copy longpath to shortpath
1185 * a) file does not exist -> return 0, LastError = ERROR_FILE_NOT_FOUND
1186 * b) file does exist -> set the short filename.
1187 * - trailing slashes are reproduced in the short name, even if the
1188 * file is not a directory
1189 * - the absolute/relative path of the short name is reproduced like found
1191 * - longpath and shortpath may have the same address
1192 * Peter Ganten, 1999
1194 DWORD WINAPI
GetShortPathNameW( LPCWSTR longpath
, LPWSTR shortpath
, DWORD shortlen
)
1196 DOS_FULL_NAME full_name
;
1197 WCHAR tmpshortpath
[MAX_PATHNAME_LEN
];
1199 DWORD sp
= 0, lp
= 0;
1203 BOOL unixabsolute
= *longpath
== '/';
1205 TRACE("%s\n", debugstr_w(longpath
));
1208 SetLastError(ERROR_INVALID_PARAMETER
);
1212 SetLastError(ERROR_BAD_PATHNAME
);
1216 /* check for drive letter */
1217 if (!unixabsolute
&& longpath
[1] == ':' ) {
1218 tmpshortpath
[0] = longpath
[0];
1219 tmpshortpath
[1] = ':';
1223 if ( ( drive
= DOSFS_GetPathDrive ( &longpath
)) == -1 ) return 0;
1224 flags
= DRIVE_GetFlags ( drive
);
1226 if (unixabsolute
&& drive
!= DRIVE_GetCurrentDrive()) {
1227 tmpshortpath
[0] = drive
+ 'A';
1228 tmpshortpath
[1] = ':';
1232 while ( longpath
[lp
] ) {
1234 /* check for path delimiters and reproduce them */
1235 if ( longpath
[lp
] == '\\' || longpath
[lp
] == '/' ) {
1236 if (!sp
|| tmpshortpath
[sp
-1]!= '\\')
1238 /* strip double "\\" */
1239 tmpshortpath
[sp
] = '\\';
1242 tmpshortpath
[sp
]=0;/*terminate string*/
1248 for(p
= longpath
+ lp
; *p
&& *p
!= '/' && *p
!= '\\'; p
++)
1250 lstrcpynW(tmpshortpath
+ sp
, longpath
+ lp
, tmplen
+ 1);
1252 /* Check, if the current element is a valid dos name */
1253 if ( DOSFS_ValidDOSName ( longpath
+ lp
, !(flags
& DRIVE_CASE_SENSITIVE
) ) ) {
1259 /* Check if the file exists and use the existing file name */
1260 if ( DOSFS_GetFullName ( tmpshortpath
, TRUE
, &full_name
) ) {
1261 strcpyW(tmpshortpath
+ sp
, strrchrW(full_name
.short_name
, '\\') + 1);
1262 sp
+= strlenW(tmpshortpath
+ sp
);
1267 TRACE("not found!\n" );
1268 SetLastError ( ERROR_FILE_NOT_FOUND
);
1271 tmpshortpath
[sp
] = 0;
1273 tmplen
= strlenW(tmpshortpath
) + 1;
1274 if (tmplen
<= shortlen
)
1276 strcpyW(shortpath
, tmpshortpath
);
1277 TRACE("returning %s\n", debugstr_w(shortpath
));
1278 tmplen
--; /* length without 0 */
1285 /***********************************************************************
1286 * GetShortPathNameA (KERNEL32.@)
1288 DWORD WINAPI
GetShortPathNameA( LPCSTR longpath
, LPSTR shortpath
, DWORD shortlen
)
1290 UNICODE_STRING longpathW
;
1291 WCHAR shortpathW
[MAX_PATH
];
1296 SetLastError(ERROR_INVALID_PARAMETER
);
1300 TRACE("%s\n", debugstr_a(longpath
));
1302 if (!RtlCreateUnicodeStringFromAsciiz(&longpathW
, longpath
))
1304 SetLastError(ERROR_NOT_ENOUGH_MEMORY
);
1308 retW
= GetShortPathNameW(longpathW
.Buffer
, shortpathW
, MAX_PATH
);
1312 else if (retW
> MAX_PATH
)
1314 SetLastError(ERROR_FILENAME_EXCED_RANGE
);
1319 ret
= WideCharToMultiByte(CP_ACP
, 0, shortpathW
, -1, NULL
, 0, NULL
, NULL
);
1320 if (ret
<= shortlen
)
1322 WideCharToMultiByte(CP_ACP
, 0, shortpathW
, -1, shortpath
, shortlen
, NULL
, NULL
);
1323 ret
--; /* length without 0 */
1327 RtlFreeUnicodeString(&longpathW
);
1332 /***********************************************************************
1333 * GetLongPathNameW (KERNEL32.@)
1336 * observed (Win2000):
1337 * shortpath=NULL: LastError=ERROR_INVALID_PARAMETER, ret=0
1338 * shortpath="": LastError=ERROR_PATH_NOT_FOUND, ret=0
1340 DWORD WINAPI
GetLongPathNameW( LPCWSTR shortpath
, LPWSTR longpath
, DWORD longlen
)
1342 DOS_FULL_NAME full_name
;
1349 SetLastError(ERROR_INVALID_PARAMETER
);
1352 if (!shortpath
[0]) {
1353 SetLastError(ERROR_PATH_NOT_FOUND
);
1357 TRACE("%s,%p,%ld\n", debugstr_w(shortpath
), longpath
, longlen
);
1359 if(shortpath
[0]=='\\' && shortpath
[1]=='\\')
1361 ERR("UNC pathname %s\n",debugstr_w(shortpath
));
1362 lstrcpynW( longpath
, full_name
.short_name
, longlen
);
1363 return strlenW(longpath
);
1366 if (!DOSFS_GetFullName( shortpath
, TRUE
, &full_name
)) return 0;
1368 root
= full_name
.long_name
;
1369 drive
= DRIVE_FindDriveRoot(&root
);
1371 ret
= MultiByteToWideChar(CP_UNIXCP
, 0, root
, -1, NULL
, 0);
1373 /* reproduce terminating slash */
1374 if (ret
> 4) /* if not drive root */
1376 len
= strlenW(shortpath
);
1377 if (shortpath
[len
- 1] == '\\' || shortpath
[len
- 1] == '/')
1383 longpath
[0] = 'A' + drive
;
1385 MultiByteToWideChar(CP_UNIXCP
, 0, root
, -1, longpath
+ 2, longlen
- 2);
1386 for (p
= longpath
; *p
; p
++) if (*p
== '/') *p
= '\\';
1389 longpath
[ret
- 2] = '\\';
1390 longpath
[ret
- 1] = 0;
1392 TRACE("returning %s\n", debugstr_w(longpath
));
1393 ret
--; /* length without 0 */
1399 /***********************************************************************
1400 * GetLongPathNameA (KERNEL32.@)
1402 DWORD WINAPI
GetLongPathNameA( LPCSTR shortpath
, LPSTR longpath
, DWORD longlen
)
1404 UNICODE_STRING shortpathW
;
1405 WCHAR longpathW
[MAX_PATH
];
1410 SetLastError(ERROR_INVALID_PARAMETER
);
1414 TRACE("%s\n", debugstr_a(shortpath
));
1416 if (!RtlCreateUnicodeStringFromAsciiz(&shortpathW
, shortpath
))
1418 SetLastError(ERROR_NOT_ENOUGH_MEMORY
);
1422 retW
= GetLongPathNameW(shortpathW
.Buffer
, longpathW
, MAX_PATH
);
1426 else if (retW
> MAX_PATH
)
1428 SetLastError(ERROR_FILENAME_EXCED_RANGE
);
1433 ret
= WideCharToMultiByte(CP_ACP
, 0, longpathW
, -1, NULL
, 0, NULL
, NULL
);
1436 WideCharToMultiByte(CP_ACP
, 0, longpathW
, -1, longpath
, longlen
, NULL
, NULL
);
1437 ret
--; /* length without 0 */
1441 RtlFreeUnicodeString(&shortpathW
);
1446 /***********************************************************************
1447 * DOSFS_DoGetFullPathName
1449 * Implementation of GetFullPathNameA/W.
1451 * bon@elektron 000331:
1452 * A test for GetFullPathName with many pathological cases
1453 * now gives identical output for Wine and OSR2
1455 static DWORD
DOSFS_DoGetFullPathName( LPCWSTR name
, DWORD len
, LPWSTR result
)
1458 DOS_FULL_NAME full_name
;
1462 WCHAR drivecur
[] = {'C',':','.',0};
1463 WCHAR driveletter
=0;
1464 int namelen
,drive
=0;
1465 static const WCHAR bkslashW
[] = {'\\',0};
1466 static const WCHAR dotW
[] = {'.',0};
1467 static const WCHAR updir_slashW
[] = {'\\','.','.','\\',0};
1468 static const WCHAR curdirW
[] = {'\\','.','\\',0};
1469 static const WCHAR updirW
[] = {'\\','.','.',0};
1473 SetLastError(ERROR_BAD_PATHNAME
);
1477 TRACE("passed %s\n", debugstr_w(name
));
1480 /*drive letter given */
1482 driveletter
= name
[0];
1484 if ((name
[1]==':') && ((name
[2]=='\\') || (name
[2]=='/')))
1485 /*absolute path given */
1487 strncpyW(full_name
.short_name
, name
, MAX_PATHNAME_LEN
);
1488 full_name
.short_name
[MAX_PATHNAME_LEN
- 1] = 0; /* ensure 0 termination */
1489 drive
= toupperW(name
[0]) - 'A';
1494 drivecur
[0]=driveletter
;
1495 else if ((name
[0]=='\\') || (name
[0]=='/'))
1496 strcpyW(drivecur
, bkslashW
);
1498 strcpyW(drivecur
, dotW
);
1500 if (!DOSFS_GetFullName( drivecur
, FALSE
, &full_name
))
1502 FIXME("internal: error getting drive/path\n");
1505 /* find path that drive letter substitutes*/
1506 drive
= toupperW(full_name
.short_name
[0]) - 'A';
1507 root
= DRIVE_GetRoot(drive
);
1510 FIXME("internal: error getting DOS Drive Root\n");
1513 if (!strcmp(root
,"/"))
1515 /* we have just the last / and we need it. */
1516 p_l
= full_name
.long_name
;
1520 p_l
= full_name
.long_name
+ strlen(root
);
1522 /* append long name (= unix name) to drive */
1523 MultiByteToWideChar(CP_UNIXCP
, 0, p_l
, -1, full_name
.short_name
+ 2, MAX_PATHNAME_LEN
- 3);
1524 /* append name to treat */
1525 namelen
= strlenW(full_name
.short_name
);
1528 p
+= 2; /* skip drive name when appending */
1529 if (namelen
+ 2 + strlenW(p
) > MAX_PATHNAME_LEN
)
1531 FIXME("internal error: buffer too small\n");
1534 full_name
.short_name
[namelen
++] ='\\';
1535 full_name
.short_name
[namelen
] = 0;
1536 strncpyW(full_name
.short_name
+ namelen
, p
, MAX_PATHNAME_LEN
- namelen
);
1537 full_name
.short_name
[MAX_PATHNAME_LEN
- 1] = 0; /* ensure 0 termination */
1539 /* reverse all slashes */
1540 for (p
=full_name
.short_name
;
1541 p
< full_name
.short_name
+ strlenW(full_name
.short_name
);
1547 /* Use memmove, as areas overlap */
1549 while ((p
= strstrW(full_name
.short_name
, updir_slashW
)))
1551 if (p
> full_name
.short_name
+2)
1554 q
= strrchrW(full_name
.short_name
, '\\');
1555 memmove(q
+1, p
+4, (strlenW(p
+4)+1) * sizeof(WCHAR
));
1559 memmove(full_name
.short_name
+3, p
+4, (strlenW(p
+4)+1) * sizeof(WCHAR
));
1562 if ((full_name
.short_name
[2]=='.')&&(full_name
.short_name
[3]=='.'))
1564 /* This case istn't treated yet : c:..\test */
1565 memmove(full_name
.short_name
+2,full_name
.short_name
+4,
1566 (strlenW(full_name
.short_name
+4)+1) * sizeof(WCHAR
));
1569 while ((p
= strstrW(full_name
.short_name
, curdirW
)))
1572 memmove(p
+1, p
+3, (strlenW(p
+3)+1) * sizeof(WCHAR
));
1574 if (!(DRIVE_GetFlags(drive
) & DRIVE_CASE_PRESERVING
))
1575 for (p
= full_name
.short_name
; *p
; p
++) *p
= toupperW(*p
);
1576 namelen
= strlenW(full_name
.short_name
);
1577 if (!strcmpW(full_name
.short_name
+namelen
-3, updirW
))
1579 /* one more strange case: "c:\test\test1\.."
1581 *(full_name
.short_name
+namelen
-3)=0;
1582 q
= strrchrW(full_name
.short_name
, '\\');
1585 if (full_name
.short_name
[namelen
-1]=='.')
1586 full_name
.short_name
[(namelen
--)-1] =0;
1588 if (full_name
.short_name
[namelen
-1]=='\\')
1589 full_name
.short_name
[(namelen
--)-1] =0;
1590 TRACE("got %s\n", debugstr_w(full_name
.short_name
));
1592 /* If the lpBuffer buffer is too small, the return value is the
1593 size of the buffer, in characters, required to hold the path
1594 plus the terminating \0 (tested against win95osr2, bon 001118)
1596 ret
= strlenW(full_name
.short_name
);
1599 /* don't touch anything when the buffer is not large enough */
1600 SetLastError( ERROR_INSUFFICIENT_BUFFER
);
1605 strncpyW( result
, full_name
.short_name
, len
);
1606 result
[len
- 1] = 0; /* ensure 0 termination */
1609 TRACE("returning %s\n", debugstr_w(full_name
.short_name
) );
1614 /***********************************************************************
1615 * GetFullPathNameA (KERNEL32.@)
1617 * if the path closed with '\', *lastpart is 0
1619 DWORD WINAPI
GetFullPathNameA( LPCSTR name
, DWORD len
, LPSTR buffer
,
1622 UNICODE_STRING nameW
;
1623 WCHAR bufferW
[MAX_PATH
];
1628 SetLastError(ERROR_INVALID_PARAMETER
);
1632 if (!RtlCreateUnicodeStringFromAsciiz(&nameW
, name
))
1634 SetLastError(ERROR_NOT_ENOUGH_MEMORY
);
1638 retW
= GetFullPathNameW( nameW
.Buffer
, MAX_PATH
, bufferW
, NULL
);
1642 else if (retW
> MAX_PATH
)
1644 SetLastError(ERROR_FILENAME_EXCED_RANGE
);
1649 ret
= WideCharToMultiByte(CP_ACP
, 0, bufferW
, -1, NULL
, 0, NULL
, NULL
);
1652 WideCharToMultiByte(CP_ACP
, 0, bufferW
, -1, buffer
, len
, NULL
, NULL
);
1653 ret
--; /* length without 0 */
1657 LPSTR p
= buffer
+ strlen(buffer
);
1661 while ((p
> buffer
+ 2) && (*p
!= '\\')) p
--;
1664 else *lastpart
= NULL
;
1669 RtlFreeUnicodeString(&nameW
);
1674 /***********************************************************************
1675 * GetFullPathNameW (KERNEL32.@)
1677 DWORD WINAPI
GetFullPathNameW( LPCWSTR name
, DWORD len
, LPWSTR buffer
,
1680 DWORD ret
= DOSFS_DoGetFullPathName( name
, len
, buffer
);
1681 if (ret
&& (ret
<=len
) && buffer
&& lastpart
)
1683 LPWSTR p
= buffer
+ strlenW(buffer
);
1684 if (*p
!= (WCHAR
)'\\')
1686 while ((p
> buffer
+ 2) && (*p
!= (WCHAR
)'\\')) p
--;
1689 else *lastpart
= NULL
;
1695 /***********************************************************************
1696 * wine_get_unix_file_name (KERNEL32.@) Not a Windows API
1698 * Return the full Unix file name for a given path.
1699 * FIXME: convert dos file name to unicode
1701 BOOL WINAPI
wine_get_unix_file_name( LPCSTR dos
, LPSTR buffer
, DWORD len
)
1705 WCHAR dosW
[MAX_PATHNAME_LEN
];
1707 MultiByteToWideChar(CP_ACP
, 0, dos
, -1, dosW
, MAX_PATHNAME_LEN
);
1708 ret
= DOSFS_GetFullName( dosW
, FALSE
, &path
);
1711 strncpy( buffer
, path
.long_name
, len
);
1712 buffer
[len
- 1] = 0; /* ensure 0 termination */
1718 /***********************************************************************
1719 * get_show_dir_symlinks_option
1721 static BOOL
get_show_dir_symlinks_option(void)
1723 static const WCHAR WineW
[] = {'M','a','c','h','i','n','e','\\',
1724 'S','o','f','t','w','a','r','e','\\',
1725 'W','i','n','e','\\','W','i','n','e','\\',
1726 'C','o','n','f','i','g','\\','W','i','n','e',0};
1727 static const WCHAR ShowDirSymlinksW
[] = {'S','h','o','w','D','i','r','S','y','m','l','i','n','k','s',0};
1732 OBJECT_ATTRIBUTES attr
;
1733 UNICODE_STRING nameW
;
1736 attr
.Length
= sizeof(attr
);
1737 attr
.RootDirectory
= 0;
1738 attr
.ObjectName
= &nameW
;
1739 attr
.Attributes
= 0;
1740 attr
.SecurityDescriptor
= NULL
;
1741 attr
.SecurityQualityOfService
= NULL
;
1742 RtlInitUnicodeString( &nameW
, WineW
);
1744 if (!NtOpenKey( &hkey
, KEY_ALL_ACCESS
, &attr
))
1746 RtlInitUnicodeString( &nameW
, ShowDirSymlinksW
);
1747 if (!NtQueryValueKey( hkey
, &nameW
, KeyValuePartialInformation
, tmp
, sizeof(tmp
), &dummy
))
1749 WCHAR
*str
= (WCHAR
*)((KEY_VALUE_PARTIAL_INFORMATION
*)tmp
)->Data
;
1750 ret
= IS_OPTION_TRUE( str
[0] );
1758 /***********************************************************************
1761 static int DOSFS_FindNextEx( FIND_FIRST_INFO
*info
, WIN32_FIND_DATAW
*entry
)
1763 UINT flags
= DRIVE_GetFlags( info
->drive
);
1764 char *p
, buffer
[MAX_PATHNAME_LEN
];
1765 const char *drive_path
;
1767 LPCWSTR long_name
, short_name
;
1768 BY_HANDLE_FILE_INFORMATION fileinfo
;
1771 drive_path
= info
->path
+ strlen(DRIVE_GetRoot( info
->drive
));
1772 while ((*drive_path
== '/') || (*drive_path
== '\\')) drive_path
++;
1773 drive_root
= !*drive_path
;
1775 lstrcpynA( buffer
, info
->path
, sizeof(buffer
) - 1 );
1776 strcat( buffer
, "/" );
1777 p
= buffer
+ strlen(buffer
);
1779 while (DOSFS_ReadDir( info
->u
.dos_dir
, &long_name
, &short_name
))
1783 /* Don't return '.' and '..' in the root of the drive */
1784 if (drive_root
&& (long_name
[0] == '.') &&
1785 (!long_name
[1] || ((long_name
[1] == '.') && !long_name
[2])))
1788 /* Check the long mask */
1790 if (info
->long_mask
&& *info
->long_mask
)
1792 if (!DOSFS_MatchLong( info
->long_mask
, long_name
,
1793 flags
& DRIVE_CASE_SENSITIVE
)) continue;
1796 /* Check the file attributes */
1797 WideCharToMultiByte(CP_UNIXCP
, 0, long_name
, -1,
1798 p
, sizeof(buffer
) - (int)(p
- buffer
), NULL
, NULL
);
1799 if (!FILE_Stat( buffer
, &fileinfo
, &is_symlink
))
1801 WARN("can't stat %s\n", buffer
);
1804 if (is_symlink
&& (fileinfo
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
))
1806 static int show_dir_symlinks
= -1;
1807 if (show_dir_symlinks
== -1)
1808 show_dir_symlinks
= get_show_dir_symlinks_option();
1809 if (!show_dir_symlinks
) continue;
1812 /* We now have a matching entry; fill the result and return */
1814 entry
->dwFileAttributes
= fileinfo
.dwFileAttributes
;
1815 entry
->ftCreationTime
= fileinfo
.ftCreationTime
;
1816 entry
->ftLastAccessTime
= fileinfo
.ftLastAccessTime
;
1817 entry
->ftLastWriteTime
= fileinfo
.ftLastWriteTime
;
1818 entry
->nFileSizeHigh
= fileinfo
.nFileSizeHigh
;
1819 entry
->nFileSizeLow
= fileinfo
.nFileSizeLow
;
1822 DOSFS_ToDosDTAFormat( short_name
, entry
->cAlternateFileName
);
1824 DOSFS_Hash( long_name
, entry
->cAlternateFileName
, FALSE
,
1825 !(flags
& DRIVE_CASE_SENSITIVE
) );
1827 lstrcpynW( entry
->cFileName
, long_name
, sizeof(entry
->cFileName
)/sizeof(entry
->cFileName
[0]) );
1828 if (!(flags
& DRIVE_CASE_PRESERVING
)) strlwrW( entry
->cFileName
);
1829 TRACE("returning %s (%s) %02lx %ld\n",
1830 debugstr_w(entry
->cFileName
), debugstr_w(entry
->cAlternateFileName
),
1831 entry
->dwFileAttributes
, entry
->nFileSizeLow
);
1834 return 0; /* End of directory */
1837 /*************************************************************************
1838 * FindFirstFileExW (KERNEL32.@)
1840 HANDLE WINAPI
FindFirstFileExW(
1842 FINDEX_INFO_LEVELS fInfoLevelId
,
1843 LPVOID lpFindFileData
,
1844 FINDEX_SEARCH_OPS fSearchOp
,
1845 LPVOID lpSearchFilter
,
1846 DWORD dwAdditionalFlags
)
1848 FIND_FIRST_INFO
*info
;
1852 SetLastError(ERROR_PATH_NOT_FOUND
);
1853 return INVALID_HANDLE_VALUE
;
1856 if ((fSearchOp
!= FindExSearchNameMatch
) || (dwAdditionalFlags
!= 0))
1858 FIXME("options not implemented 0x%08x 0x%08lx\n", fSearchOp
, dwAdditionalFlags
);
1859 return INVALID_HANDLE_VALUE
;
1862 switch(fInfoLevelId
)
1864 case FindExInfoStandard
:
1866 WIN32_FIND_DATAW
* data
= (WIN32_FIND_DATAW
*) lpFindFileData
;
1870 data
->dwReserved0
= data
->dwReserved1
= 0x0;
1871 if (lpFileName
[0] == '\\' && lpFileName
[1] == '\\')
1873 ERR("UNC path name\n");
1874 if (!(info
= HeapAlloc( GetProcessHeap(), 0, sizeof(FIND_FIRST_INFO
)))) break;
1875 info
->u
.smb_dir
= SMB_FindFirst(lpFileName
);
1876 if(!info
->u
.smb_dir
)
1878 HeapFree(GetProcessHeap(), 0, info
);
1882 RtlInitializeCriticalSection( &info
->cs
);
1886 DOS_FULL_NAME full_name
;
1888 if (lpFileName
[0] && lpFileName
[1] == ':')
1890 /* don't allow root directories */
1891 if (!lpFileName
[2] ||
1892 ((lpFileName
[2] == '/' || lpFileName
[2] == '\\') && !lpFileName
[3]))
1894 SetLastError(ERROR_FILE_NOT_FOUND
);
1895 return INVALID_HANDLE_VALUE
;
1898 if (!DOSFS_GetFullName( lpFileName
, FALSE
, &full_name
)) break;
1899 if (!(info
= HeapAlloc( GetProcessHeap(), 0, sizeof(FIND_FIRST_INFO
)))) break;
1900 RtlInitializeCriticalSection( &info
->cs
);
1901 info
->path
= HeapAlloc( GetProcessHeap(), 0, strlen(full_name
.long_name
)+1 );
1902 strcpy( info
->path
, full_name
.long_name
);
1904 p
= strrchr( info
->path
, '/' );
1906 long_mask_len
= MultiByteToWideChar(CP_UNIXCP
, 0, p
, -1, NULL
, 0);
1907 info
->long_mask
= HeapAlloc( GetProcessHeap(), 0, long_mask_len
* sizeof(WCHAR
) );
1908 MultiByteToWideChar(CP_UNIXCP
, 0, p
, -1, info
->long_mask
, long_mask_len
);
1910 info
->drive
= full_name
.drive
;
1913 info
->u
.dos_dir
= DOSFS_OpenDir( info
->path
);
1915 if (!FindNextFileW( (HANDLE
) info
, data
))
1917 FindClose( (HANDLE
) info
);
1918 SetLastError( ERROR_FILE_NOT_FOUND
);
1921 return (HANDLE
) info
;
1925 FIXME("fInfoLevelId 0x%08x not implemented\n", fInfoLevelId
);
1927 return INVALID_HANDLE_VALUE
;
1930 /*************************************************************************
1931 * FindFirstFileA (KERNEL32.@)
1933 HANDLE WINAPI
FindFirstFileA(
1935 WIN32_FIND_DATAA
*lpFindData
)
1937 return FindFirstFileExA(lpFileName
, FindExInfoStandard
, lpFindData
,
1938 FindExSearchNameMatch
, NULL
, 0);
1941 /*************************************************************************
1942 * FindFirstFileExA (KERNEL32.@)
1944 HANDLE WINAPI
FindFirstFileExA(
1946 FINDEX_INFO_LEVELS fInfoLevelId
,
1947 LPVOID lpFindFileData
,
1948 FINDEX_SEARCH_OPS fSearchOp
,
1949 LPVOID lpSearchFilter
,
1950 DWORD dwAdditionalFlags
)
1953 WIN32_FIND_DATAA
*dataA
;
1954 WIN32_FIND_DATAW dataW
;
1955 UNICODE_STRING pathW
;
1959 SetLastError(ERROR_PATH_NOT_FOUND
);
1960 return INVALID_HANDLE_VALUE
;
1963 if (!RtlCreateUnicodeStringFromAsciiz(&pathW
, lpFileName
))
1965 SetLastError(ERROR_NOT_ENOUGH_MEMORY
);
1966 return INVALID_HANDLE_VALUE
;
1969 handle
= FindFirstFileExW(pathW
.Buffer
, fInfoLevelId
, &dataW
, fSearchOp
, lpSearchFilter
, dwAdditionalFlags
);
1970 RtlFreeUnicodeString(&pathW
);
1971 if (handle
== INVALID_HANDLE_VALUE
) return handle
;
1973 dataA
= (WIN32_FIND_DATAA
*) lpFindFileData
;
1974 dataA
->dwFileAttributes
= dataW
.dwFileAttributes
;
1975 dataA
->ftCreationTime
= dataW
.ftCreationTime
;
1976 dataA
->ftLastAccessTime
= dataW
.ftLastAccessTime
;
1977 dataA
->ftLastWriteTime
= dataW
.ftLastWriteTime
;
1978 dataA
->nFileSizeHigh
= dataW
.nFileSizeHigh
;
1979 dataA
->nFileSizeLow
= dataW
.nFileSizeLow
;
1980 WideCharToMultiByte( CP_ACP
, 0, dataW
.cFileName
, -1,
1981 dataA
->cFileName
, sizeof(dataA
->cFileName
), NULL
, NULL
);
1982 WideCharToMultiByte( CP_ACP
, 0, dataW
.cAlternateFileName
, -1,
1983 dataA
->cAlternateFileName
, sizeof(dataA
->cAlternateFileName
), NULL
, NULL
);
1987 /*************************************************************************
1988 * FindFirstFileW (KERNEL32.@)
1990 HANDLE WINAPI
FindFirstFileW( LPCWSTR lpFileName
, WIN32_FIND_DATAW
*lpFindData
)
1992 return FindFirstFileExW(lpFileName
, FindExInfoStandard
, lpFindData
,
1993 FindExSearchNameMatch
, NULL
, 0);
1996 /*************************************************************************
1997 * FindNextFileW (KERNEL32.@)
1999 BOOL WINAPI
FindNextFileW( HANDLE handle
, WIN32_FIND_DATAW
*data
)
2001 FIND_FIRST_INFO
*info
;
2003 DWORD gle
= ERROR_NO_MORE_FILES
;
2005 if (handle
== INVALID_HANDLE_VALUE
)
2007 SetLastError( ERROR_INVALID_HANDLE
);
2010 info
= (FIND_FIRST_INFO
*) handle
;
2011 RtlEnterCriticalSection( &info
->cs
);
2012 if (info
->drive
== -1)
2014 ret
= SMB_FindNext( info
->u
.smb_dir
, data
);
2017 SMB_CloseDir( info
->u
.smb_dir
);
2018 HeapFree( GetProcessHeap(), 0, info
->path
);
2022 else if (!info
->path
|| !info
->u
.dos_dir
)
2026 else if (!DOSFS_FindNextEx( info
, data
))
2028 DOSFS_CloseDir( info
->u
.dos_dir
); info
->u
.dos_dir
= NULL
;
2029 HeapFree( GetProcessHeap(), 0, info
->path
);
2031 HeapFree( GetProcessHeap(), 0, info
->long_mask
);
2032 info
->long_mask
= NULL
;
2037 RtlLeaveCriticalSection( &info
->cs
);
2038 if( !ret
) SetLastError( gle
);
2043 /*************************************************************************
2044 * FindNextFileA (KERNEL32.@)
2046 BOOL WINAPI
FindNextFileA( HANDLE handle
, WIN32_FIND_DATAA
*data
)
2048 WIN32_FIND_DATAW dataW
;
2049 if (!FindNextFileW( handle
, &dataW
)) return FALSE
;
2050 data
->dwFileAttributes
= dataW
.dwFileAttributes
;
2051 data
->ftCreationTime
= dataW
.ftCreationTime
;
2052 data
->ftLastAccessTime
= dataW
.ftLastAccessTime
;
2053 data
->ftLastWriteTime
= dataW
.ftLastWriteTime
;
2054 data
->nFileSizeHigh
= dataW
.nFileSizeHigh
;
2055 data
->nFileSizeLow
= dataW
.nFileSizeLow
;
2056 WideCharToMultiByte( CP_ACP
, 0, dataW
.cFileName
, -1,
2057 data
->cFileName
, sizeof(data
->cFileName
), NULL
, NULL
);
2058 WideCharToMultiByte( CP_ACP
, 0, dataW
.cAlternateFileName
, -1,
2059 data
->cAlternateFileName
,
2060 sizeof(data
->cAlternateFileName
), NULL
, NULL
);
2064 /*************************************************************************
2065 * FindClose (KERNEL32.@)
2067 BOOL WINAPI
FindClose( HANDLE handle
)
2069 FIND_FIRST_INFO
*info
= (FIND_FIRST_INFO
*) handle
;
2071 if (handle
== INVALID_HANDLE_VALUE
) goto error
;
2075 RtlEnterCriticalSection( &info
->cs
);
2078 if (info
->u
.dos_dir
) DOSFS_CloseDir( info
->u
.dos_dir
);
2079 if (info
->path
) HeapFree( GetProcessHeap(), 0, info
->path
);
2080 if (info
->long_mask
) HeapFree( GetProcessHeap(), 0, info
->long_mask
);
2083 __EXCEPT(page_fault
)
2085 WARN("Illegal handle %p\n", handle
);
2086 SetLastError( ERROR_INVALID_HANDLE
);
2090 if (!info
) goto error
;
2091 RtlLeaveCriticalSection( &info
->cs
);
2092 RtlDeleteCriticalSection( &info
->cs
);
2093 HeapFree(GetProcessHeap(), 0, info
);
2097 SetLastError( ERROR_INVALID_HANDLE
);
2101 /***********************************************************************
2102 * MulDiv (KERNEL32.@)
2104 * Result of multiplication and division
2105 * -1: Overflow occurred or Divisor was 0
2112 #if SIZEOF_LONG_LONG >= 8
2115 if (!nDivisor
) return -1;
2117 /* We want to deal with a positive divisor to simplify the logic. */
2120 nMultiplicand
= - nMultiplicand
;
2121 nDivisor
= -nDivisor
;
2124 /* If the result is positive, we "add" to round. else, we subtract to round. */
2125 if ( ( (nMultiplicand
< 0) && (nMultiplier
< 0) ) ||
2126 ( (nMultiplicand
>= 0) && (nMultiplier
>= 0) ) )
2127 ret
= (((long long)nMultiplicand
* nMultiplier
) + (nDivisor
/2)) / nDivisor
;
2129 ret
= (((long long)nMultiplicand
* nMultiplier
) - (nDivisor
/2)) / nDivisor
;
2131 if ((ret
> 2147483647) || (ret
< -2147483647)) return -1;
2134 if (!nDivisor
) return -1;
2136 /* We want to deal with a positive divisor to simplify the logic. */
2139 nMultiplicand
= - nMultiplicand
;
2140 nDivisor
= -nDivisor
;
2143 /* If the result is positive, we "add" to round. else, we subtract to round. */
2144 if ( ( (nMultiplicand
< 0) && (nMultiplier
< 0) ) ||
2145 ( (nMultiplicand
>= 0) && (nMultiplier
>= 0) ) )
2146 return ((nMultiplicand
* nMultiplier
) + (nDivisor
/2)) / nDivisor
;
2148 return ((nMultiplicand
* nMultiplier
) - (nDivisor
/2)) / nDivisor
;
2154 /***********************************************************************
2155 * DosDateTimeToFileTime (KERNEL32.@)
2157 BOOL WINAPI
DosDateTimeToFileTime( WORD fatdate
, WORD fattime
, LPFILETIME ft
)
2162 time_t time1
, time2
;
2165 newtm
.tm_sec
= (fattime
& 0x1f) * 2;
2166 newtm
.tm_min
= (fattime
>> 5) & 0x3f;
2167 newtm
.tm_hour
= (fattime
>> 11);
2168 newtm
.tm_mday
= (fatdate
& 0x1f);
2169 newtm
.tm_mon
= ((fatdate
>> 5) & 0x0f) - 1;
2170 newtm
.tm_year
= (fatdate
>> 9) + 80;
2172 RtlSecondsSince1970ToTime( timegm(&newtm
), (LARGE_INTEGER
*)ft
);
2174 time1
= mktime(&newtm
);
2175 gtm
= gmtime(&time1
);
2176 time2
= mktime(gtm
);
2177 RtlSecondsSince1970ToTime( 2*time1
-time2
, (LARGE_INTEGER
*)ft
);
2183 /***********************************************************************
2184 * FileTimeToDosDateTime (KERNEL32.@)
2186 BOOL WINAPI
FileTimeToDosDateTime( const FILETIME
*ft
, LPWORD fatdate
,
2194 li
.s
.LowPart
= ft
->dwLowDateTime
;
2195 li
.s
.HighPart
= ft
->dwHighDateTime
;
2196 RtlTimeToSecondsSince1970( &li
, &t
);
2198 tm
= gmtime( &unixtime
);
2200 *fattime
= (tm
->tm_hour
<< 11) + (tm
->tm_min
<< 5) + (tm
->tm_sec
/ 2);
2202 *fatdate
= ((tm
->tm_year
- 80) << 9) + ((tm
->tm_mon
+ 1) << 5)
2208 /***********************************************************************
2209 * QueryDosDeviceA (KERNEL32.@)
2211 * returns array of strings terminated by \0, terminated by \0
2213 DWORD WINAPI
QueryDosDeviceA(LPCSTR devname
,LPSTR target
,DWORD bufsize
)
2215 DWORD ret
= 0, retW
;
2216 LPWSTR targetW
= (LPWSTR
)HeapAlloc(GetProcessHeap(),0,
2217 bufsize
* sizeof(WCHAR
));
2218 UNICODE_STRING devnameW
;
2220 if(devname
) RtlCreateUnicodeStringFromAsciiz(&devnameW
, devname
);
2221 else devnameW
.Buffer
= NULL
;
2223 retW
= QueryDosDeviceW(devnameW
.Buffer
, targetW
, bufsize
);
2225 ret
= WideCharToMultiByte(CP_ACP
, 0, targetW
, retW
, target
,
2226 bufsize
, NULL
, NULL
);
2228 RtlFreeUnicodeString(&devnameW
);
2229 if (targetW
) HeapFree(GetProcessHeap(),0,targetW
);
2234 /***********************************************************************
2235 * QueryDosDeviceW (KERNEL32.@)
2237 * returns array of strings terminated by \0, terminated by \0
2240 * - Win9x returns for all calls ERROR_INVALID_PARAMETER
2241 * - the returned devices for devname == NULL is far from complete
2242 * - its not checked that the returned device exist
2244 DWORD WINAPI
QueryDosDeviceW(LPCWSTR devname
,LPWSTR target
,DWORD bufsize
)
2246 const WCHAR
*pDev
, *pName
, *pNum
= NULL
;
2250 TRACE("(%s,...)\n", debugstr_w(devname
));
2252 /* return known MSDOS devices */
2255 static const WCHAR devices
[][5] = {{'A','U','X',0},
2256 {'C','O','M','1',0},
2257 {'C','O','M','2',0},
2258 {'L','P','T','1',0},
2260 for(i
=0; (i
< (sizeof(devices
)/sizeof(devices
[0]))); i
++) {
2261 DWORD len
= strlenW(devices
[i
]);
2262 if(target
&& (bufsize
>= ret
+ len
+ 2)) {
2263 strcpyW(target
+ret
, devices
[i
]);
2266 /* in this case WinXP returns 0 */
2267 FIXME("function return is wrong for WinXP!\n");
2268 SetLastError(ERROR_INSUFFICIENT_BUFFER
);
2272 /* append drives here */
2273 if(target
&& bufsize
> 0) target
[ret
++] = 0;
2274 FIXME("Returned list is not complete\n");
2277 /* In theory all that are possible and have been defined.
2278 * Now just those below, since mirc uses it to check for special files.
2280 * (It is more complex, and supports netmounted stuff, and \\.\ stuff,
2281 * but currently we just ignore that.)
2283 if (!strcmpiW(devname
, auxW
)) {
2288 } else if (!strcmpiW(devname
, nulW
)) {
2291 } else if (!strncmpiW(devname
, comW
, strlenW(comW
))) {
2294 pNum
= devname
+ strlenW(comW
);
2295 for(numsiz
=0; isdigitW(*(pNum
+numsiz
)); numsiz
++);
2296 if(*(pNum
+ numsiz
)) {
2297 SetLastError(ERROR_FILE_NOT_FOUND
);
2300 } else if (!strncmpiW(devname
, lptW
, strlenW(lptW
))) {
2303 pNum
= devname
+ strlenW(lptW
);
2304 for(numsiz
=0; isdigitW(*(pNum
+numsiz
)); numsiz
++);
2305 if(*(pNum
+ numsiz
)) {
2306 SetLastError(ERROR_FILE_NOT_FOUND
);
2310 /* This might be a DOS device we do not handle yet ... */
2311 FIXME("(%s) not detected as DOS device!\n",debugstr_w(devname
));
2313 /* Win9x set the error ERROR_INVALID_PARAMETER */
2314 SetLastError(ERROR_FILE_NOT_FOUND
);
2317 FIXME("device %s may not exist on this computer\n", debugstr_w(devname
));
2319 ret
= strlenW(pDev
) + strlenW(pName
) + numsiz
+ 2;
2320 if (ret
> bufsize
) ret
= 0;
2321 if (target
&& ret
) {
2322 strcpyW(target
,pDev
);
2323 strcatW(target
,pName
);
2324 if (pNum
) strcatW(target
,pNum
);