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
23 #include "wine/port.h"
25 #include <sys/types.h>
29 #ifdef HAVE_SYS_ERRNO_H
30 #include <sys/errno.h>
37 #ifdef HAVE_SYS_IOCTL_H
38 #include <sys/ioctl.h>
45 #define NONAMELESSUNION
46 #define NONAMELESSSTRUCT
53 #include "wine/unicode.h"
54 #include "wine/winbase16.h"
58 #include "wine/server.h"
59 #include "wine/exception.h"
64 #include "wine/debug.h"
66 WINE_DEFAULT_DEBUG_CHANNEL(dosfs
);
67 WINE_DECLARE_DEBUG_CHANNEL(file
);
69 /* Define the VFAT ioctl to get both short and long file names */
70 /* FIXME: is it possible to get this to work on other systems? */
72 /* We want the real kernel dirent structure, not the libc one */
77 unsigned short d_reclen
;
81 #define VFAT_IOCTL_READDIR_BOTH _IOR('r', 1, KERNEL_DIRENT [2] )
83 /* To avoid blocking on non-directories in DOSFS_OpenDir_VFAT*/
85 # define O_DIRECTORY 0200000 /* must be directory */
89 #undef VFAT_IOCTL_READDIR_BOTH /* just in case... */
92 #define IS_OPTION_TRUE(ch) ((ch) == 'y' || (ch) == 'Y' || (ch) == 't' || (ch) == 'T' || (ch) == '1')
94 /* Chars we don't want to see in DOS file names */
95 #define INVALID_DOS_CHARS "*?<>|\"+=,;[] \345"
97 static const DOS_DEVICE DOSFS_Devices
[] =
98 /* name, device flags (see Int 21/AX=0x4400) */
100 { {'C','O','N',0}, 0xc0d3 },
101 { {'P','R','N',0}, 0xa0c0 },
102 { {'N','U','L',0}, 0x80c4 },
103 { {'A','U','X',0}, 0x80c0 },
104 { {'L','P','T','1',0}, 0xa0c0 },
105 { {'L','P','T','2',0}, 0xa0c0 },
106 { {'L','P','T','3',0}, 0xa0c0 },
107 { {'L','P','T','4',0}, 0xc0d3 },
108 { {'C','O','M','1',0}, 0x80c0 },
109 { {'C','O','M','2',0}, 0x80c0 },
110 { {'C','O','M','3',0}, 0x80c0 },
111 { {'C','O','M','4',0}, 0x80c0 },
112 { {'S','C','S','I','M','G','R','$',0}, 0xc0c0 },
113 { {'H','P','S','C','A','N',0}, 0xc0c0 },
114 { {'E','M','M','X','X','X','X','0',0}, 0x0000 }
117 static const WCHAR devW
[] = {'\\','D','e','v','i','c','e','\\',0};
118 static const WCHAR dosW
[] = {'\\','D','o','s','D','e','v','i','c','e','s','\\',0};
120 static const WCHAR auxW
[] = {'A','U','X',0};
121 static const WCHAR comW
[] = {'C','O','M',0};
122 static const WCHAR lptW
[] = {'L','P','T',0};
123 static const WCHAR nulW
[] = {'N','U','L',0};
125 static const WCHAR nullW
[] = {'N','u','l','l',0};
126 static const WCHAR parW
[] = {'P','a','r','a','l','l','e','l',0};
127 static const WCHAR serW
[] = {'S','e','r','i','a','l',0};
128 static const WCHAR oneW
[] = {'1',0};
131 * Directory info for DOSFS_ReadDir
132 * contains the names of *all* the files in the directory
141 /* Info structure for FindFirstFile handle */
144 char *path
; /* unix path */
157 static WINE_EXCEPTION_FILTER(page_fault
)
159 if (GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION
)
160 return EXCEPTION_EXECUTE_HANDLER
;
161 return EXCEPTION_CONTINUE_SEARCH
;
165 /***********************************************************************
168 * Return 1 if Unix file 'name' is also a valid MS-DOS name
169 * (i.e. contains only valid DOS chars, lower-case only, fits in 8.3 format).
170 * File name can be terminated by '\0', '\\' or '/'.
172 static int DOSFS_ValidDOSName( LPCWSTR name
, int ignore_case
)
174 static const char invalid_chars
[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" INVALID_DOS_CHARS
;
175 const WCHAR
*p
= name
;
176 const char *invalid
= ignore_case
? (invalid_chars
+ 26) : invalid_chars
;
181 /* Check for "." and ".." */
184 /* All other names beginning with '.' are invalid */
185 return (IS_END_OF_NAME(*p
));
187 while (!IS_END_OF_NAME(*p
))
189 if (*p
< 256 && strchr( invalid
, (char)*p
)) return 0; /* Invalid char */
190 if (*p
== '.') break; /* Start of the extension */
191 if (++len
> 8) return 0; /* Name too long */
194 if (*p
!= '.') return 1; /* End of name */
196 if (IS_END_OF_NAME(*p
)) return 0; /* Empty extension not allowed */
198 while (!IS_END_OF_NAME(*p
))
200 if (*p
< 256 && strchr( invalid
, (char)*p
)) return 0; /* Invalid char */
201 if (*p
== '.') return 0; /* Second extension not allowed */
202 if (++len
> 3) return 0; /* Extension too long */
209 /***********************************************************************
210 * DOSFS_ToDosFCBFormat
212 * Convert a file name to DOS FCB format (8+3 chars, padded with blanks),
213 * expanding wild cards and converting to upper-case in the process.
214 * File name can be terminated by '\0', '\\' or '/'.
215 * Return FALSE if the name is not a valid DOS name.
216 * 'buffer' must be at least 12 characters long.
218 BOOL
DOSFS_ToDosFCBFormat( LPCWSTR name
, LPWSTR buffer
)
220 static const char invalid_chars
[] = INVALID_DOS_CHARS
;
224 /* Check for "." and ".." */
229 for(i
= 1; i
< 11; i
++) buffer
[i
] = ' ';
236 return (!*p
|| (*p
== '/') || (*p
== '\\'));
239 for (i
= 0; i
< 8; i
++)
256 if (*p
< 256 && strchr( invalid_chars
, (char)*p
)) return FALSE
;
257 buffer
[i
] = toupperW(*p
);
265 /* Skip all chars after wildcard up to first dot */
266 while (*p
&& (*p
!= '/') && (*p
!= '\\') && (*p
!= '.')) p
++;
270 /* Check if name too long */
271 if (*p
&& (*p
!= '/') && (*p
!= '\\') && (*p
!= '.')) return FALSE
;
273 if (*p
== '.') p
++; /* Skip dot */
275 for (i
= 8; i
< 11; i
++)
285 return FALSE
; /* Second extension not allowed */
293 if (*p
< 256 && strchr( invalid_chars
, (char)*p
)) return FALSE
;
294 buffer
[i
] = toupperW(*p
);
301 /* at most 3 character of the extension are processed
302 * is something behind this ?
304 while (*p
== '*' || *p
== ' ') p
++; /* skip wildcards and spaces */
305 return IS_END_OF_NAME(*p
);
309 /***********************************************************************
310 * DOSFS_ToDosDTAFormat
312 * Convert a file name from FCB to DTA format (name.ext, null-terminated)
313 * converting to upper-case in the process.
314 * File name can be terminated by '\0', '\\' or '/'.
315 * 'buffer' must be at least 13 characters long.
317 static void DOSFS_ToDosDTAFormat( LPCWSTR name
, LPWSTR buffer
)
321 memcpy( buffer
, name
, 8 * sizeof(WCHAR
) );
323 while ((p
> buffer
) && (p
[-1] == ' ')) p
--;
325 memcpy( p
, name
+ 8, 3 * sizeof(WCHAR
) );
327 while (p
[-1] == ' ') p
--;
328 if (p
[-1] == '.') p
--;
333 /***********************************************************************
336 * Check a long file name against a mask.
338 * Tests (done in W95 DOS shell - case insensitive):
339 * *.txt test1.test.txt *
341 * *.t??????.t* test1.ta.tornado.txt *
342 * *tornado* test1.ta.tornado.txt *
343 * t*t test1.ta.tornado.txt *
345 * ?est??? test1.txt -
346 * *test1.txt* test1.txt *
347 * h?l?o*t.dat hellothisisatest.dat *
349 static int DOSFS_MatchLong( LPCWSTR mask
, LPCWSTR name
, int case_sensitive
)
351 LPCWSTR lastjoker
= NULL
;
352 LPCWSTR next_to_retry
= NULL
;
353 static const WCHAR asterisk_dot_asterisk
[] = {'*','.','*',0};
355 TRACE("(%s, %s, %x)\n", debugstr_w(mask
), debugstr_w(name
), case_sensitive
);
357 if (!strcmpW( mask
, asterisk_dot_asterisk
)) return 1;
358 while (*name
&& *mask
)
363 while (*mask
== '*') mask
++; /* Skip consecutive '*' */
365 if (!*mask
) return 1; /* end of mask is all '*', so match */
367 /* skip to the next match after the joker(s) */
368 if (case_sensitive
) while (*name
&& (*name
!= *mask
)) name
++;
369 else while (*name
&& (toupperW(*name
) != toupperW(*mask
))) name
++;
372 next_to_retry
= name
;
374 else if (*mask
!= '?')
379 if (*mask
!= *name
) mismatch
= 1;
383 if (toupperW(*mask
) != toupperW(*name
)) mismatch
= 1;
397 else /* mismatch ! */
399 if (lastjoker
) /* we had an '*', so we can try unlimitedly */
403 /* this scan sequence was a mismatch, so restart
404 * 1 char after the first char we checked last time */
406 name
= next_to_retry
;
409 return 0; /* bad luck */
418 while ((*mask
== '.') || (*mask
== '*'))
419 mask
++; /* Ignore trailing '.' or '*' in mask */
420 return (!*name
&& !*mask
);
424 /***********************************************************************
427 * Used to construct an array of filenames in DOSFS_OpenDir
429 static BOOL
DOSFS_AddDirEntry(DOS_DIR
**dir
, LPCWSTR name
, LPCWSTR dosname
)
431 int extra1
= strlenW(name
) + 1;
432 int extra2
= strlenW(dosname
) + 1;
434 /* if we need more, at minimum double the size */
435 if( (extra1
+ extra2
+ (*dir
)->used
) > (*dir
)->size
)
437 int more
= (*dir
)->size
;
440 if(more
<(extra1
+extra2
))
441 more
= extra1
+extra2
;
443 t
= HeapReAlloc(GetProcessHeap(), 0, *dir
, sizeof(**dir
) +
444 ((*dir
)->size
+ more
)*sizeof(WCHAR
) );
447 SetLastError( ERROR_NOT_ENOUGH_MEMORY
);
448 ERR("Out of memory caching directory structure %d %d %d\n",
449 (*dir
)->size
, more
, (*dir
)->used
);
453 (*dir
)->size
+= more
;
456 /* at this point, the dir structure is big enough to hold these names */
457 strcpyW(&(*dir
)->names
[(*dir
)->used
], name
);
458 (*dir
)->used
+= extra1
;
459 strcpyW(&(*dir
)->names
[(*dir
)->used
], dosname
);
460 (*dir
)->used
+= extra2
;
466 /***********************************************************************
469 static BOOL
DOSFS_OpenDir_VFAT(DOS_DIR
**dir
, const char *unix_path
)
471 #ifdef VFAT_IOCTL_READDIR_BOTH
473 int fd
= open( unix_path
, O_RDONLY
|O_DIRECTORY
);
476 /* Check if the VFAT ioctl is supported on this directory */
483 WCHAR long_name
[MAX_PATH
];
484 WCHAR short_name
[12];
486 r
= (ioctl( fd
, VFAT_IOCTL_READDIR_BOTH
, (long)de
) != -1);
491 MultiByteToWideChar(CP_UNIXCP
, 0, de
[0].d_name
, -1, long_name
, MAX_PATH
);
492 if (!DOSFS_ToDosFCBFormat( long_name
, short_name
))
493 short_name
[0] = '\0';
495 MultiByteToWideChar(CP_UNIXCP
, 0, de
[1].d_name
, -1, long_name
, MAX_PATH
);
497 MultiByteToWideChar(CP_UNIXCP
, 0, de
[0].d_name
, -1, long_name
, MAX_PATH
);
498 r
= DOSFS_AddDirEntry(dir
, long_name
, short_name
);
504 static const WCHAR empty_strW
[] = { 0 };
505 DOSFS_AddDirEntry(dir
, empty_strW
, empty_strW
);
511 #endif /* VFAT_IOCTL_READDIR_BOTH */
515 /***********************************************************************
516 * DOSFS_OpenDir_Normal
518 * Now use the standard opendir/readdir interface
520 static BOOL
DOSFS_OpenDir_Normal( DOS_DIR
**dir
, const char *unix_path
)
522 DIR *unixdir
= opendir( unix_path
);
524 static const WCHAR empty_strW
[] = { 0 };
530 WCHAR long_name
[MAX_PATH
];
531 struct dirent
*de
= readdir(unixdir
);
535 MultiByteToWideChar(CP_UNIXCP
, 0, de
->d_name
, -1, long_name
, MAX_PATH
);
536 r
= DOSFS_AddDirEntry(dir
, long_name
, empty_strW
);
541 DOSFS_AddDirEntry(dir
, empty_strW
, empty_strW
);
546 /***********************************************************************
549 static DOS_DIR
*DOSFS_OpenDir( const char *unix_path
)
551 const int init_size
= 0x100;
552 DOS_DIR
*dir
= HeapAlloc( GetProcessHeap(), 0, sizeof(*dir
) + init_size
*sizeof (WCHAR
));
555 TRACE("%s\n",debugstr_a(unix_path
));
559 SetLastError( ERROR_NOT_ENOUGH_MEMORY
);
563 dir
->size
= init_size
;
565 /* Treat empty path as root directory. This simplifies path split into
566 directory and mask in several other places */
567 if (!*unix_path
) unix_path
= "/";
569 r
= DOSFS_OpenDir_VFAT( &dir
, unix_path
);
572 r
= DOSFS_OpenDir_Normal( &dir
, unix_path
);
576 HeapFree(GetProcessHeap(), 0, dir
);
585 /***********************************************************************
588 static void DOSFS_CloseDir( DOS_DIR
*dir
)
590 HeapFree( GetProcessHeap(), 0, dir
);
594 /***********************************************************************
597 static BOOL
DOSFS_ReadDir( DOS_DIR
*dir
, LPCWSTR
*long_name
,
598 LPCWSTR
*short_name
)
605 /* the long pathname is first */
606 ln
= &dir
->names
[dir
->used
];
611 dir
->used
+= (strlenW(ln
) + 1);
613 /* followed by the short path name */
614 sn
= &dir
->names
[dir
->used
];
619 dir
->used
+= (strlenW(sn
) + 1);
625 /***********************************************************************
628 * Transform a Unix file name into a hashed DOS name. If the name is a valid
629 * DOS name, it is converted to upper-case; otherwise it is replaced by a
630 * hashed version that fits in 8.3 format.
631 * File name can be terminated by '\0', '\\' or '/'.
632 * 'buffer' must be at least 13 characters long.
634 static void DOSFS_Hash( LPCWSTR name
, LPWSTR buffer
, BOOL dir_format
,
637 static const char invalid_chars
[] = INVALID_DOS_CHARS
"~.";
638 static const char hash_chars
[32] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345";
647 for(i
= 0; i
< 11; i
++) buffer
[i
] = ' ';
651 if (DOSFS_ValidDOSName( name
, ignore_case
))
653 /* Check for '.' and '..' */
657 if (!dir_format
) buffer
[1] = buffer
[2] = '\0';
658 if (name
[1] == '.') buffer
[1] = '.';
662 /* Simply copy the name, converting to uppercase */
664 for (dst
= buffer
; !IS_END_OF_NAME(*name
) && (*name
!= '.'); name
++)
665 *dst
++ = toupperW(*name
);
668 if (dir_format
) dst
= buffer
+ 8;
670 for (name
++; !IS_END_OF_NAME(*name
); name
++)
671 *dst
++ = toupperW(*name
);
673 if (!dir_format
) *dst
= '\0';
677 /* Compute the hash code of the file name */
678 /* If you know something about hash functions, feel free to */
679 /* insert a better algorithm here... */
682 for (p
= name
, hash
= 0xbeef; !IS_END_OF_NAME(p
[1]); p
++)
683 hash
= (hash
<<3) ^ (hash
>>5) ^ tolowerW(*p
) ^ (tolowerW(p
[1]) << 8);
684 hash
= (hash
<<3) ^ (hash
>>5) ^ tolowerW(*p
); /* Last character */
688 for (p
= name
, hash
= 0xbeef; !IS_END_OF_NAME(p
[1]); p
++)
689 hash
= (hash
<< 3) ^ (hash
>> 5) ^ *p
^ (p
[1] << 8);
690 hash
= (hash
<< 3) ^ (hash
>> 5) ^ *p
; /* Last character */
693 /* Find last dot for start of the extension */
694 for (p
= name
+1, ext
= NULL
; !IS_END_OF_NAME(*p
); p
++)
695 if (*p
== '.') ext
= p
;
696 if (ext
&& IS_END_OF_NAME(ext
[1]))
697 ext
= NULL
; /* Empty extension ignored */
699 /* Copy first 4 chars, replacing invalid chars with '_' */
700 for (i
= 4, p
= name
, dst
= buffer
; i
> 0; i
--, p
++)
702 if (IS_END_OF_NAME(*p
) || (p
== ext
)) break;
703 *dst
++ = (*p
< 256 && strchr( invalid_chars
, (char)*p
)) ? '_' : toupperW(*p
);
705 /* Pad to 5 chars with '~' */
706 while (i
-- >= 0) *dst
++ = '~';
708 /* Insert hash code converted to 3 ASCII chars */
709 *dst
++ = hash_chars
[(hash
>> 10) & 0x1f];
710 *dst
++ = hash_chars
[(hash
>> 5) & 0x1f];
711 *dst
++ = hash_chars
[hash
& 0x1f];
713 /* Copy the first 3 chars of the extension (if any) */
716 if (!dir_format
) *dst
++ = '.';
717 for (i
= 3, ext
++; (i
> 0) && !IS_END_OF_NAME(*ext
); i
--, ext
++)
718 *dst
++ = (*ext
< 256 && strchr( invalid_chars
, (char)*ext
)) ? '_' : toupperW(*ext
);
720 if (!dir_format
) *dst
= '\0';
724 /***********************************************************************
727 * Find the Unix file name in a given directory that corresponds to
728 * a file name (either in Unix or DOS format).
729 * File name can be terminated by '\0', '\\' or '/'.
730 * Return TRUE if OK, FALSE if no file name matches.
732 * 'long_buf' must be at least 'long_len' characters long. If the long name
733 * turns out to be larger than that, the function returns FALSE.
734 * 'short_buf' must be at least 13 characters long.
736 BOOL
DOSFS_FindUnixName( const DOS_FULL_NAME
*path
, LPCWSTR name
, char *long_buf
,
737 INT long_len
, LPWSTR short_buf
, BOOL ignore_case
)
740 LPCWSTR long_name
, short_name
;
741 WCHAR dos_name
[12], tmp_buf
[13];
744 LPCWSTR p
= strchrW( name
, '/' );
745 int len
= p
? (int)(p
- name
) : strlenW(name
);
746 if ((p
= strchrW( name
, '\\' ))) len
= min( (int)(p
- name
), len
);
747 /* Ignore trailing dots and spaces */
748 while (len
> 1 && (name
[len
-1] == '.' || name
[len
-1] == ' ')) len
--;
749 if (long_len
< len
+ 1) return FALSE
;
751 TRACE("%s,%s\n", path
->long_name
, debugstr_w(name
) );
753 if (!DOSFS_ToDosFCBFormat( name
, dos_name
)) dos_name
[0] = '\0';
755 if (!(dir
= DOSFS_OpenDir( path
->long_name
)))
757 WARN("(%s,%s): can't open dir: %s\n",
758 path
->long_name
, debugstr_w(name
), strerror(errno
) );
762 while ((ret
= DOSFS_ReadDir( dir
, &long_name
, &short_name
)))
764 /* Check against Unix name */
765 if (len
== strlenW(long_name
))
769 if (!strncmpW( long_name
, name
, len
)) break;
773 if (!strncmpiW( long_name
, name
, len
)) break;
778 /* Check against hashed DOS name */
781 DOSFS_Hash( long_name
, tmp_buf
, TRUE
, ignore_case
);
782 short_name
= tmp_buf
;
784 if (!strcmpW( dos_name
, short_name
)) break;
789 if (long_buf
) WideCharToMultiByte(CP_UNIXCP
, 0, long_name
, -1, long_buf
, long_len
, NULL
, NULL
);
793 DOSFS_ToDosDTAFormat( short_name
, short_buf
);
795 DOSFS_Hash( long_name
, short_buf
, FALSE
, ignore_case
);
797 TRACE("(%s,%s) -> %s (%s)\n", path
->long_name
, debugstr_w(name
),
798 debugstr_w(long_name
), short_buf
? debugstr_w(short_buf
) : "***");
801 WARN("%s not found in '%s'\n", debugstr_w(name
), path
->long_name
);
802 DOSFS_CloseDir( dir
);
807 /***********************************************************************
810 * Check if a DOS file name represents a DOS device and return the device.
812 const DOS_DEVICE
*DOSFS_GetDevice( LPCWSTR name
)
817 if (!name
) return NULL
; /* if wine_server_handle_to_fd was used */
818 if (name
[0] && (name
[1] == ':')) name
+= 2;
819 if ((p
= strrchrW( name
, '/' ))) name
= p
+ 1;
820 if ((p
= strrchrW( name
, '\\' ))) name
= p
+ 1;
821 for (i
= 0; i
< sizeof(DOSFS_Devices
)/sizeof(DOSFS_Devices
[0]); i
++)
823 const WCHAR
*dev
= DOSFS_Devices
[i
].name
;
824 if (!strncmpiW( dev
, name
, strlenW(dev
) ))
826 p
= name
+ strlenW( dev
);
827 if (!*p
|| (*p
== '.') || (*p
== ':')) return &DOSFS_Devices
[i
];
834 /***********************************************************************
835 * DOSFS_GetDeviceByHandle
837 const DOS_DEVICE
*DOSFS_GetDeviceByHandle( HANDLE hFile
)
839 const DOS_DEVICE
*ret
= NULL
;
840 SERVER_START_REQ( get_device_id
)
843 if (!wine_server_call( req
))
845 if ((reply
->id
>= 0) &&
846 (reply
->id
< sizeof(DOSFS_Devices
)/sizeof(DOSFS_Devices
[0])))
847 ret
= &DOSFS_Devices
[reply
->id
];
855 /**************************************************************************
856 * DOSFS_CreateCommPort
858 static HANDLE
DOSFS_CreateCommPort(LPCWSTR name
, DWORD access
, DWORD attributes
, LPSECURITY_ATTRIBUTES sa
)
863 OBJECT_ATTRIBUTES attr
;
864 UNICODE_STRING nameW
;
869 static const WCHAR serialportsW
[] = {'M','a','c','h','i','n','e','\\',
870 'S','o','f','t','w','a','r','e','\\',
871 'W','i','n','e','\\','W','i','n','e','\\',
872 'C','o','n','f','i','g','\\',
873 'S','e','r','i','a','l','P','o','r','t','s',0};
875 TRACE_(file
)("%s %lx %lx\n", debugstr_w(name
), access
, attributes
);
877 attr
.Length
= sizeof(attr
);
878 attr
.RootDirectory
= 0;
879 attr
.ObjectName
= &nameW
;
881 attr
.SecurityDescriptor
= NULL
;
882 attr
.SecurityQualityOfService
= NULL
;
883 RtlInitUnicodeString( &nameW
, serialportsW
);
885 if (NtOpenKey( &hkey
, KEY_ALL_ACCESS
, &attr
)) return 0;
887 RtlInitUnicodeString( &nameW
, name
);
888 if (!NtQueryValueKey( hkey
, &nameW
, KeyValuePartialInformation
, tmp
, sizeof(tmp
), &dummy
))
889 devnameW
= (WCHAR
*)((KEY_VALUE_PARTIAL_INFORMATION
*)tmp
)->Data
;
895 if (!devnameW
) return 0;
896 WideCharToMultiByte(CP_ACP
, 0, devnameW
, -1, devname
, sizeof(devname
), NULL
, NULL
);
898 TRACE("opening %s as %s\n", devname
, debugstr_w(name
));
900 SERVER_START_REQ( create_serial
)
902 req
->access
= access
;
903 req
->inherit
= (sa
&& (sa
->nLength
>=sizeof(*sa
)) && sa
->bInheritHandle
);
904 req
->attributes
= attributes
;
905 req
->sharing
= FILE_SHARE_READ
|FILE_SHARE_WRITE
;
906 wine_server_add_data( req
, devname
, strlen(devname
) );
908 wine_server_call_err( req
);
914 ERR("Couldn't open device '%s' ! (check permissions)\n",devname
);
916 TRACE("return %p\n", ret
);
920 /***********************************************************************
923 * Open a DOS device. This might not map 1:1 into the UNIX device concept.
924 * Returns 0 on failure.
926 HANDLE
DOSFS_OpenDevice( LPCWSTR name
, DWORD access
, DWORD attributes
, LPSECURITY_ATTRIBUTES sa
)
932 if (name
[0] && (name
[1] == ':')) name
+= 2;
933 if ((p
= strrchrW( name
, '/' ))) name
= p
+ 1;
934 if ((p
= strrchrW( name
, '\\' ))) name
= p
+ 1;
935 for (i
= 0; i
< sizeof(DOSFS_Devices
)/sizeof(DOSFS_Devices
[0]); i
++)
937 const WCHAR
*dev
= DOSFS_Devices
[i
].name
;
938 if (!strncmpiW( dev
, name
, strlenW(dev
) ))
940 p
= name
+ strlenW( dev
);
941 if (!*p
|| (*p
== '.') || (*p
== ':')) {
942 static const WCHAR nulW
[] = {'N','U','L',0};
943 static const WCHAR conW
[] = {'C','O','N',0};
944 static const WCHAR scsimgrW
[] = {'S','C','S','I','M','G','R','$',0};
945 static const WCHAR hpscanW
[] = {'H','P','S','C','A','N',0};
946 static const WCHAR emmxxxx0W
[] = {'E','M','M','X','X','X','X','0',0};
948 if (!strcmpiW(DOSFS_Devices
[i
].name
, nulW
))
949 return FILE_CreateFile( "/dev/null", access
,
950 FILE_SHARE_READ
|FILE_SHARE_WRITE
, sa
,
951 OPEN_EXISTING
, 0, 0, TRUE
, DRIVE_UNKNOWN
);
952 if (!strcmpiW(DOSFS_Devices
[i
].name
, conW
)) {
954 switch (access
& (GENERIC_READ
|GENERIC_WRITE
)) {
956 to_dup
= GetStdHandle( STD_INPUT_HANDLE
);
959 to_dup
= GetStdHandle( STD_OUTPUT_HANDLE
);
962 FIXME("can't open CON read/write\n");
965 if (!DuplicateHandle( GetCurrentProcess(), to_dup
, GetCurrentProcess(),
967 sa
&& (sa
->nLength
>=sizeof(*sa
)) && sa
->bInheritHandle
,
968 DUPLICATE_SAME_ACCESS
))
972 if (!strcmpiW(DOSFS_Devices
[i
].name
, scsimgrW
) ||
973 !strcmpiW(DOSFS_Devices
[i
].name
, hpscanW
) ||
974 !strcmpiW(DOSFS_Devices
[i
].name
, emmxxxx0W
))
976 return FILE_CreateDevice( i
, access
, sa
);
979 if( (handle
=DOSFS_CreateCommPort(DOSFS_Devices
[i
].name
,access
,attributes
,sa
)) )
981 FIXME("device open %s not supported (yet)\n", debugstr_w(DOSFS_Devices
[i
].name
));
990 /***********************************************************************
993 * Get the drive specified by a given path name (DOS or Unix format).
995 static int DOSFS_GetPathDrive( LPCWSTR
*name
)
1000 if (*p
&& (p
[1] == ':'))
1002 drive
= toupperW(*p
) - 'A';
1005 else if (*p
== '/') /* Absolute Unix path? */
1007 if ((drive
= DRIVE_FindDriveRootW( name
)) == -1)
1009 MESSAGE("Warning: %s not accessible from a configured DOS drive\n", debugstr_w(*name
) );
1010 /* Assume it really was a DOS name */
1011 drive
= DRIVE_GetCurrentDrive();
1014 else drive
= DRIVE_GetCurrentDrive();
1016 if (!DRIVE_IsValid(drive
))
1018 SetLastError( ERROR_INVALID_DRIVE
);
1025 /***********************************************************************
1028 * Convert a file name (DOS or mixed DOS/Unix format) to a valid
1029 * Unix name / short DOS name pair.
1030 * Return FALSE if one of the path components does not exist. The last path
1031 * component is only checked if 'check_last' is non-zero.
1032 * The buffers pointed to by 'long_buf' and 'short_buf' must be
1033 * at least MAX_PATHNAME_LEN long.
1035 BOOL
DOSFS_GetFullName( LPCWSTR name
, BOOL check_last
, DOS_FULL_NAME
*full
)
1041 static const WCHAR driveA_rootW
[] = {'A',':','\\',0};
1042 static const WCHAR dos_rootW
[] = {'\\',0};
1044 TRACE("%s (last=%d)\n", debugstr_w(name
), check_last
);
1046 if ((!*name
) || (*name
=='\n'))
1047 { /* error code for Win98 */
1048 SetLastError(ERROR_BAD_PATHNAME
);
1052 if ((full
->drive
= DOSFS_GetPathDrive( &name
)) == -1) return FALSE
;
1053 flags
= DRIVE_GetFlags( full
->drive
);
1055 lstrcpynA( full
->long_name
, DRIVE_GetRoot( full
->drive
),
1056 sizeof(full
->long_name
) );
1057 if (full
->long_name
[1]) root
= full
->long_name
+ strlen(full
->long_name
);
1058 else root
= full
->long_name
; /* root directory */
1060 strcpyW( full
->short_name
, driveA_rootW
);
1061 full
->short_name
[0] += full
->drive
;
1063 if ((*name
== '\\') || (*name
== '/')) /* Absolute path */
1065 while ((*name
== '\\') || (*name
== '/')) name
++;
1067 else /* Relative path */
1069 lstrcpynA( root
+ 1, DRIVE_GetUnixCwd( full
->drive
),
1070 sizeof(full
->long_name
) - (root
- full
->long_name
) - 1 );
1071 if (root
[1]) *root
= '/';
1072 lstrcpynW( full
->short_name
+ 3, DRIVE_GetDosCwd( full
->drive
),
1073 sizeof(full
->short_name
)/sizeof(full
->short_name
[0]) - 3 );
1076 p_l
= full
->long_name
[1] ? full
->long_name
+ strlen(full
->long_name
)
1078 p_s
= full
->short_name
[3] ? full
->short_name
+ strlenW(full
->short_name
)
1079 : full
->short_name
+ 2;
1082 while (*name
&& found
)
1084 /* Check for '.' and '..' */
1088 if (IS_END_OF_NAME(name
[1]))
1091 while ((*name
== '\\') || (*name
== '/')) name
++;
1094 else if ((name
[1] == '.') && IS_END_OF_NAME(name
[2]))
1097 while ((*name
== '\\') || (*name
== '/')) name
++;
1098 while ((p_l
> root
) && (*p_l
!= '/')) p_l
--;
1099 while ((p_s
> full
->short_name
+ 2) && (*p_s
!= '\\')) p_s
--;
1100 *p_l
= *p_s
= '\0'; /* Remove trailing separator */
1105 /* Make sure buffers are large enough */
1107 if ((p_s
>= full
->short_name
+ sizeof(full
->short_name
)/sizeof(full
->short_name
[0]) - 14) ||
1108 (p_l
>= full
->long_name
+ sizeof(full
->long_name
) - 1))
1110 SetLastError( ERROR_PATH_NOT_FOUND
);
1114 /* Get the long and short name matching the file name */
1116 if ((found
= DOSFS_FindUnixName( full
, name
, p_l
+ 1,
1117 sizeof(full
->long_name
) - (p_l
- full
->long_name
) - 1,
1118 p_s
+ 1, !(flags
& DRIVE_CASE_SENSITIVE
) )))
1123 p_s
+= strlenW(p_s
);
1124 while (!IS_END_OF_NAME(*name
)) name
++;
1126 else if (!check_last
)
1130 while (!IS_END_OF_NAME(*name
) &&
1131 (p_s
< full
->short_name
+ sizeof(full
->short_name
)/sizeof(full
->short_name
[0]) - 1) &&
1132 (p_l
< full
->long_name
+ sizeof(full
->long_name
) - 1))
1135 *p_s
++ = tolowerW(*name
);
1136 /* If the drive is case-sensitive we want to create new */
1137 /* files in lower-case otherwise we can't reopen them */
1138 /* under the same short name. */
1139 if (flags
& DRIVE_CASE_SENSITIVE
) wch
= tolowerW(*name
);
1141 p_l
+= WideCharToMultiByte(CP_UNIXCP
, 0, &wch
, 1, p_l
, 2, NULL
, NULL
);
1144 /* Ignore trailing dots and spaces */
1145 while(p_l
[-1] == '.' || p_l
[-1] == ' ') {
1152 while ((*name
== '\\') || (*name
== '/')) name
++;
1159 SetLastError( ERROR_FILE_NOT_FOUND
);
1162 if (*name
) /* Not last */
1164 SetLastError( ERROR_PATH_NOT_FOUND
);
1168 if (!full
->long_name
[0]) strcpy( full
->long_name
, "/" );
1169 if (!full
->short_name
[2]) strcpyW( full
->short_name
+ 2, dos_rootW
);
1170 TRACE("returning %s = %s\n", full
->long_name
, debugstr_w(full
->short_name
) );
1175 /***********************************************************************
1176 * GetShortPathNameW (KERNEL32.@)
1180 * longpath=NULL: LastError=ERROR_INVALID_PARAMETER, ret=0
1181 * longpath="" or invalid: LastError=ERROR_BAD_PATHNAME, ret=0
1183 * more observations ( with NT 3.51 (WinDD) ):
1184 * longpath <= 8.3 -> just copy longpath to shortpath
1186 * a) file does not exist -> return 0, LastError = ERROR_FILE_NOT_FOUND
1187 * b) file does exist -> set the short filename.
1188 * - trailing slashes are reproduced in the short name, even if the
1189 * file is not a directory
1190 * - the absolute/relative path of the short name is reproduced like found
1192 * - longpath and shortpath may have the same address
1193 * Peter Ganten, 1999
1195 DWORD WINAPI
GetShortPathNameW( LPCWSTR longpath
, LPWSTR shortpath
, DWORD shortlen
)
1197 DOS_FULL_NAME full_name
;
1198 WCHAR tmpshortpath
[MAX_PATHNAME_LEN
];
1200 DWORD sp
= 0, lp
= 0;
1204 BOOL unixabsolute
= *longpath
== '/';
1206 TRACE("%s\n", debugstr_w(longpath
));
1209 SetLastError(ERROR_INVALID_PARAMETER
);
1213 SetLastError(ERROR_BAD_PATHNAME
);
1217 /* check for drive letter */
1218 if (!unixabsolute
&& longpath
[1] == ':' ) {
1219 tmpshortpath
[0] = longpath
[0];
1220 tmpshortpath
[1] = ':';
1224 if ( ( drive
= DOSFS_GetPathDrive ( &longpath
)) == -1 ) return 0;
1225 flags
= DRIVE_GetFlags ( drive
);
1227 if (unixabsolute
&& drive
!= DRIVE_GetCurrentDrive()) {
1228 tmpshortpath
[0] = drive
+ 'A';
1229 tmpshortpath
[1] = ':';
1233 while ( longpath
[lp
] ) {
1235 /* check for path delimiters and reproduce them */
1236 if ( longpath
[lp
] == '\\' || longpath
[lp
] == '/' ) {
1237 if (!sp
|| tmpshortpath
[sp
-1]!= '\\')
1239 /* strip double "\\" */
1240 tmpshortpath
[sp
] = '\\';
1243 tmpshortpath
[sp
]=0;/*terminate string*/
1249 for(p
= longpath
+ lp
; *p
&& *p
!= '/' && *p
!= '\\'; p
++)
1251 lstrcpynW(tmpshortpath
+ sp
, longpath
+ lp
, tmplen
+ 1);
1253 /* Check, if the current element is a valid dos name */
1254 if ( DOSFS_ValidDOSName ( longpath
+ lp
, !(flags
& DRIVE_CASE_SENSITIVE
) ) ) {
1260 /* Check if the file exists and use the existing file name */
1261 if ( DOSFS_GetFullName ( tmpshortpath
, TRUE
, &full_name
) ) {
1262 strcpyW(tmpshortpath
+ sp
, strrchrW(full_name
.short_name
, '\\') + 1);
1263 sp
+= strlenW(tmpshortpath
+ sp
);
1268 TRACE("not found!\n" );
1269 SetLastError ( ERROR_FILE_NOT_FOUND
);
1272 tmpshortpath
[sp
] = 0;
1274 tmplen
= strlenW(tmpshortpath
) + 1;
1275 if (tmplen
<= shortlen
)
1277 strcpyW(shortpath
, tmpshortpath
);
1278 TRACE("returning %s\n", debugstr_w(shortpath
));
1279 tmplen
--; /* length without 0 */
1286 /***********************************************************************
1287 * GetShortPathNameA (KERNEL32.@)
1289 DWORD WINAPI
GetShortPathNameA( LPCSTR longpath
, LPSTR shortpath
, DWORD shortlen
)
1291 UNICODE_STRING longpathW
;
1292 WCHAR shortpathW
[MAX_PATH
];
1297 SetLastError(ERROR_INVALID_PARAMETER
);
1301 TRACE("%s\n", debugstr_a(longpath
));
1303 if (!RtlCreateUnicodeStringFromAsciiz(&longpathW
, longpath
))
1305 SetLastError(ERROR_NOT_ENOUGH_MEMORY
);
1309 retW
= GetShortPathNameW(longpathW
.Buffer
, shortpathW
, MAX_PATH
);
1313 else if (retW
> MAX_PATH
)
1315 SetLastError(ERROR_FILENAME_EXCED_RANGE
);
1320 ret
= WideCharToMultiByte(CP_ACP
, 0, shortpathW
, -1, NULL
, 0, NULL
, NULL
);
1321 if (ret
<= shortlen
)
1323 WideCharToMultiByte(CP_ACP
, 0, shortpathW
, -1, shortpath
, shortlen
, NULL
, NULL
);
1324 ret
--; /* length without 0 */
1328 RtlFreeUnicodeString(&longpathW
);
1333 /***********************************************************************
1334 * GetLongPathNameW (KERNEL32.@)
1337 * observed (Win2000):
1338 * shortpath=NULL: LastError=ERROR_INVALID_PARAMETER, ret=0
1339 * shortpath="": LastError=ERROR_PATH_NOT_FOUND, ret=0
1341 DWORD WINAPI
GetLongPathNameW( LPCWSTR shortpath
, LPWSTR longpath
, DWORD longlen
)
1343 DOS_FULL_NAME full_name
;
1350 SetLastError(ERROR_INVALID_PARAMETER
);
1353 if (!shortpath
[0]) {
1354 SetLastError(ERROR_PATH_NOT_FOUND
);
1358 TRACE("%s,%p,%ld\n", debugstr_w(shortpath
), longpath
, longlen
);
1360 if(shortpath
[0]=='\\' && shortpath
[1]=='\\')
1362 ERR("UNC pathname %s\n",debugstr_w(shortpath
));
1363 lstrcpynW( longpath
, full_name
.short_name
, longlen
);
1364 return strlenW(longpath
);
1367 if (!DOSFS_GetFullName( shortpath
, TRUE
, &full_name
)) return 0;
1369 root
= full_name
.long_name
;
1370 drive
= DRIVE_FindDriveRoot(&root
);
1372 ret
= MultiByteToWideChar(CP_UNIXCP
, 0, root
, -1, NULL
, 0);
1374 /* reproduce terminating slash */
1375 if (ret
> 4) /* if not drive root */
1377 len
= strlenW(shortpath
);
1378 if (shortpath
[len
- 1] == '\\' || shortpath
[len
- 1] == '/')
1384 longpath
[0] = 'A' + drive
;
1386 MultiByteToWideChar(CP_UNIXCP
, 0, root
, -1, longpath
+ 2, longlen
- 2);
1387 for (p
= longpath
; *p
; p
++) if (*p
== '/') *p
= '\\';
1390 longpath
[ret
- 2] = '\\';
1391 longpath
[ret
- 1] = 0;
1393 TRACE("returning %s\n", debugstr_w(longpath
));
1394 ret
--; /* length without 0 */
1400 /***********************************************************************
1401 * GetLongPathNameA (KERNEL32.@)
1403 DWORD WINAPI
GetLongPathNameA( LPCSTR shortpath
, LPSTR longpath
, DWORD longlen
)
1405 UNICODE_STRING shortpathW
;
1406 WCHAR longpathW
[MAX_PATH
];
1411 SetLastError(ERROR_INVALID_PARAMETER
);
1415 TRACE("%s\n", debugstr_a(shortpath
));
1417 if (!RtlCreateUnicodeStringFromAsciiz(&shortpathW
, shortpath
))
1419 SetLastError(ERROR_NOT_ENOUGH_MEMORY
);
1423 retW
= GetLongPathNameW(shortpathW
.Buffer
, longpathW
, MAX_PATH
);
1427 else if (retW
> MAX_PATH
)
1429 SetLastError(ERROR_FILENAME_EXCED_RANGE
);
1434 ret
= WideCharToMultiByte(CP_ACP
, 0, longpathW
, -1, NULL
, 0, NULL
, NULL
);
1437 WideCharToMultiByte(CP_ACP
, 0, longpathW
, -1, longpath
, longlen
, NULL
, NULL
);
1438 ret
--; /* length without 0 */
1442 RtlFreeUnicodeString(&shortpathW
);
1447 /***********************************************************************
1448 * DOSFS_DoGetFullPathName
1450 * Implementation of GetFullPathNameA/W.
1452 * bon@elektron 000331:
1453 * A test for GetFullPathName with many pathological cases
1454 * now gives identical output for Wine and OSR2
1456 static DWORD
DOSFS_DoGetFullPathName( LPCWSTR name
, DWORD len
, LPWSTR result
)
1459 DOS_FULL_NAME full_name
;
1463 WCHAR drivecur
[] = {'C',':','.',0};
1464 WCHAR driveletter
=0;
1465 int namelen
,drive
=0;
1466 static const WCHAR bkslashW
[] = {'\\',0};
1467 static const WCHAR dotW
[] = {'.',0};
1468 static const WCHAR updir_slashW
[] = {'\\','.','.','\\',0};
1469 static const WCHAR curdirW
[] = {'\\','.','\\',0};
1470 static const WCHAR updirW
[] = {'\\','.','.',0};
1474 SetLastError(ERROR_BAD_PATHNAME
);
1478 TRACE("passed %s\n", debugstr_w(name
));
1481 /*drive letter given */
1483 driveletter
= name
[0];
1485 if ((name
[1]==':') && ((name
[2]=='\\') || (name
[2]=='/')))
1486 /*absolute path given */
1488 strncpyW(full_name
.short_name
, name
, MAX_PATHNAME_LEN
);
1489 full_name
.short_name
[MAX_PATHNAME_LEN
- 1] = 0; /* ensure 0 termination */
1490 drive
= toupperW(name
[0]) - 'A';
1495 drivecur
[0]=driveletter
;
1496 else if ((name
[0]=='\\') || (name
[0]=='/'))
1497 strcpyW(drivecur
, bkslashW
);
1499 strcpyW(drivecur
, dotW
);
1501 if (!DOSFS_GetFullName( drivecur
, FALSE
, &full_name
))
1503 FIXME("internal: error getting drive/path\n");
1506 /* find path that drive letter substitutes*/
1507 drive
= toupperW(full_name
.short_name
[0]) - 'A';
1508 root
= DRIVE_GetRoot(drive
);
1511 FIXME("internal: error getting DOS Drive Root\n");
1514 if (!strcmp(root
,"/"))
1516 /* we have just the last / and we need it. */
1517 p_l
= full_name
.long_name
;
1521 p_l
= full_name
.long_name
+ strlen(root
);
1523 /* append long name (= unix name) to drive */
1524 MultiByteToWideChar(CP_UNIXCP
, 0, p_l
, -1, full_name
.short_name
+ 2, MAX_PATHNAME_LEN
- 3);
1525 /* append name to treat */
1526 namelen
= strlenW(full_name
.short_name
);
1529 p
+= 2; /* skip drive name when appending */
1530 if (namelen
+ 2 + strlenW(p
) > MAX_PATHNAME_LEN
)
1532 FIXME("internal error: buffer too small\n");
1535 full_name
.short_name
[namelen
++] ='\\';
1536 full_name
.short_name
[namelen
] = 0;
1537 strncpyW(full_name
.short_name
+ namelen
, p
, MAX_PATHNAME_LEN
- namelen
);
1538 full_name
.short_name
[MAX_PATHNAME_LEN
- 1] = 0; /* ensure 0 termination */
1540 /* reverse all slashes */
1541 for (p
=full_name
.short_name
;
1542 p
< full_name
.short_name
+ strlenW(full_name
.short_name
);
1548 /* Use memmove, as areas overlap */
1550 while ((p
= strstrW(full_name
.short_name
, updir_slashW
)))
1552 if (p
> full_name
.short_name
+2)
1555 q
= strrchrW(full_name
.short_name
, '\\');
1556 memmove(q
+1, p
+4, (strlenW(p
+4)+1) * sizeof(WCHAR
));
1560 memmove(full_name
.short_name
+3, p
+4, (strlenW(p
+4)+1) * sizeof(WCHAR
));
1563 if ((full_name
.short_name
[2]=='.')&&(full_name
.short_name
[3]=='.'))
1565 /* This case istn't treated yet : c:..\test */
1566 memmove(full_name
.short_name
+2,full_name
.short_name
+4,
1567 (strlenW(full_name
.short_name
+4)+1) * sizeof(WCHAR
));
1570 while ((p
= strstrW(full_name
.short_name
, curdirW
)))
1573 memmove(p
+1, p
+3, (strlenW(p
+3)+1) * sizeof(WCHAR
));
1575 if (!(DRIVE_GetFlags(drive
) & DRIVE_CASE_PRESERVING
))
1576 for (p
= full_name
.short_name
; *p
; p
++) *p
= toupperW(*p
);
1577 namelen
= strlenW(full_name
.short_name
);
1578 if (!strcmpW(full_name
.short_name
+namelen
-3, updirW
))
1580 /* one more strange case: "c:\test\test1\.."
1582 *(full_name
.short_name
+namelen
-3)=0;
1583 q
= strrchrW(full_name
.short_name
, '\\');
1586 if (full_name
.short_name
[namelen
-1]=='.')
1587 full_name
.short_name
[(namelen
--)-1] =0;
1589 if (full_name
.short_name
[namelen
-1]=='\\')
1590 full_name
.short_name
[(namelen
--)-1] =0;
1591 TRACE("got %s\n", debugstr_w(full_name
.short_name
));
1593 /* If the lpBuffer buffer is too small, the return value is the
1594 size of the buffer, in characters, required to hold the path
1595 plus the terminating \0 (tested against win95osr2, bon 001118)
1597 ret
= strlenW(full_name
.short_name
);
1600 /* don't touch anything when the buffer is not large enough */
1601 SetLastError( ERROR_INSUFFICIENT_BUFFER
);
1606 strncpyW( result
, full_name
.short_name
, len
);
1607 result
[len
- 1] = 0; /* ensure 0 termination */
1610 TRACE("returning %s\n", debugstr_w(full_name
.short_name
) );
1615 /***********************************************************************
1616 * GetFullPathNameA (KERNEL32.@)
1618 * if the path closed with '\', *lastpart is 0
1620 DWORD WINAPI
GetFullPathNameA( LPCSTR name
, DWORD len
, LPSTR buffer
,
1623 UNICODE_STRING nameW
;
1624 WCHAR bufferW
[MAX_PATH
];
1629 SetLastError(ERROR_INVALID_PARAMETER
);
1633 if (!RtlCreateUnicodeStringFromAsciiz(&nameW
, name
))
1635 SetLastError(ERROR_NOT_ENOUGH_MEMORY
);
1639 retW
= GetFullPathNameW( nameW
.Buffer
, MAX_PATH
, bufferW
, NULL
);
1643 else if (retW
> MAX_PATH
)
1645 SetLastError(ERROR_FILENAME_EXCED_RANGE
);
1650 ret
= WideCharToMultiByte(CP_ACP
, 0, bufferW
, -1, NULL
, 0, NULL
, NULL
);
1653 WideCharToMultiByte(CP_ACP
, 0, bufferW
, -1, buffer
, len
, NULL
, NULL
);
1654 ret
--; /* length without 0 */
1658 LPSTR p
= buffer
+ strlen(buffer
);
1662 while ((p
> buffer
+ 2) && (*p
!= '\\')) p
--;
1665 else *lastpart
= NULL
;
1670 RtlFreeUnicodeString(&nameW
);
1675 /***********************************************************************
1676 * GetFullPathNameW (KERNEL32.@)
1678 DWORD WINAPI
GetFullPathNameW( LPCWSTR name
, DWORD len
, LPWSTR buffer
,
1681 DWORD ret
= DOSFS_DoGetFullPathName( name
, len
, buffer
);
1682 if (ret
&& (ret
<=len
) && buffer
&& lastpart
)
1684 LPWSTR p
= buffer
+ strlenW(buffer
);
1685 if (*p
!= (WCHAR
)'\\')
1687 while ((p
> buffer
+ 2) && (*p
!= (WCHAR
)'\\')) p
--;
1690 else *lastpart
= NULL
;
1696 /***********************************************************************
1697 * wine_get_unix_file_name (KERNEL32.@) Not a Windows API
1699 * Return the full Unix file name for a given path.
1700 * FIXME: convert dos file name to unicode
1702 BOOL WINAPI
wine_get_unix_file_name( LPCSTR dos
, LPSTR buffer
, DWORD len
)
1706 WCHAR dosW
[MAX_PATHNAME_LEN
];
1708 MultiByteToWideChar(CP_ACP
, 0, dos
, -1, dosW
, MAX_PATHNAME_LEN
);
1709 ret
= DOSFS_GetFullName( dosW
, FALSE
, &path
);
1712 strncpy( buffer
, path
.long_name
, len
);
1713 buffer
[len
- 1] = 0; /* ensure 0 termination */
1719 /***********************************************************************
1720 * get_show_dir_symlinks_option
1722 static BOOL
get_show_dir_symlinks_option(void)
1724 static const WCHAR WineW
[] = {'M','a','c','h','i','n','e','\\',
1725 'S','o','f','t','w','a','r','e','\\',
1726 'W','i','n','e','\\','W','i','n','e','\\',
1727 'C','o','n','f','i','g','\\','W','i','n','e',0};
1728 static const WCHAR ShowDirSymlinksW
[] = {'S','h','o','w','D','i','r','S','y','m','l','i','n','k','s',0};
1733 OBJECT_ATTRIBUTES attr
;
1734 UNICODE_STRING nameW
;
1737 attr
.Length
= sizeof(attr
);
1738 attr
.RootDirectory
= 0;
1739 attr
.ObjectName
= &nameW
;
1740 attr
.Attributes
= 0;
1741 attr
.SecurityDescriptor
= NULL
;
1742 attr
.SecurityQualityOfService
= NULL
;
1743 RtlInitUnicodeString( &nameW
, WineW
);
1745 if (!NtOpenKey( &hkey
, KEY_ALL_ACCESS
, &attr
))
1747 RtlInitUnicodeString( &nameW
, ShowDirSymlinksW
);
1748 if (!NtQueryValueKey( hkey
, &nameW
, KeyValuePartialInformation
, tmp
, sizeof(tmp
), &dummy
))
1750 WCHAR
*str
= (WCHAR
*)((KEY_VALUE_PARTIAL_INFORMATION
*)tmp
)->Data
;
1751 ret
= IS_OPTION_TRUE( str
[0] );
1759 /***********************************************************************
1762 static int DOSFS_FindNextEx( FIND_FIRST_INFO
*info
, WIN32_FIND_DATAW
*entry
)
1764 UINT flags
= DRIVE_GetFlags( info
->drive
);
1765 char *p
, buffer
[MAX_PATHNAME_LEN
];
1766 const char *drive_path
;
1768 LPCWSTR long_name
, short_name
;
1769 BY_HANDLE_FILE_INFORMATION fileinfo
;
1772 drive_path
= info
->path
+ strlen(DRIVE_GetRoot( info
->drive
));
1773 while ((*drive_path
== '/') || (*drive_path
== '\\')) drive_path
++;
1774 drive_root
= !*drive_path
;
1776 lstrcpynA( buffer
, info
->path
, sizeof(buffer
) - 1 );
1777 strcat( buffer
, "/" );
1778 p
= buffer
+ strlen(buffer
);
1780 while (DOSFS_ReadDir( info
->u
.dos_dir
, &long_name
, &short_name
))
1784 /* Don't return '.' and '..' in the root of the drive */
1785 if (drive_root
&& (long_name
[0] == '.') &&
1786 (!long_name
[1] || ((long_name
[1] == '.') && !long_name
[2])))
1789 /* Check the long mask */
1791 if (info
->long_mask
&& *info
->long_mask
)
1793 if (!DOSFS_MatchLong( info
->long_mask
, long_name
,
1794 flags
& DRIVE_CASE_SENSITIVE
)) continue;
1797 /* Check the file attributes */
1798 WideCharToMultiByte(CP_UNIXCP
, 0, long_name
, -1,
1799 p
, sizeof(buffer
) - (int)(p
- buffer
), NULL
, NULL
);
1800 if (!FILE_Stat( buffer
, &fileinfo
, &is_symlink
))
1802 WARN("can't stat %s\n", buffer
);
1805 if (is_symlink
&& (fileinfo
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
))
1807 static int show_dir_symlinks
= -1;
1808 if (show_dir_symlinks
== -1)
1809 show_dir_symlinks
= get_show_dir_symlinks_option();
1810 if (!show_dir_symlinks
) continue;
1813 /* We now have a matching entry; fill the result and return */
1815 entry
->dwFileAttributes
= fileinfo
.dwFileAttributes
;
1816 entry
->ftCreationTime
= fileinfo
.ftCreationTime
;
1817 entry
->ftLastAccessTime
= fileinfo
.ftLastAccessTime
;
1818 entry
->ftLastWriteTime
= fileinfo
.ftLastWriteTime
;
1819 entry
->nFileSizeHigh
= fileinfo
.nFileSizeHigh
;
1820 entry
->nFileSizeLow
= fileinfo
.nFileSizeLow
;
1823 DOSFS_ToDosDTAFormat( short_name
, entry
->cAlternateFileName
);
1825 DOSFS_Hash( long_name
, entry
->cAlternateFileName
, FALSE
,
1826 !(flags
& DRIVE_CASE_SENSITIVE
) );
1828 lstrcpynW( entry
->cFileName
, long_name
, sizeof(entry
->cFileName
)/sizeof(entry
->cFileName
[0]) );
1829 if (!(flags
& DRIVE_CASE_PRESERVING
)) strlwrW( entry
->cFileName
);
1830 TRACE("returning %s (%s) %02lx %ld\n",
1831 debugstr_w(entry
->cFileName
), debugstr_w(entry
->cAlternateFileName
),
1832 entry
->dwFileAttributes
, entry
->nFileSizeLow
);
1835 return 0; /* End of directory */
1838 /*************************************************************************
1839 * FindFirstFileExW (KERNEL32.@)
1841 HANDLE WINAPI
FindFirstFileExW(
1843 FINDEX_INFO_LEVELS fInfoLevelId
,
1844 LPVOID lpFindFileData
,
1845 FINDEX_SEARCH_OPS fSearchOp
,
1846 LPVOID lpSearchFilter
,
1847 DWORD dwAdditionalFlags
)
1849 FIND_FIRST_INFO
*info
;
1853 SetLastError(ERROR_PATH_NOT_FOUND
);
1854 return INVALID_HANDLE_VALUE
;
1857 if ((fSearchOp
!= FindExSearchNameMatch
) || (dwAdditionalFlags
!= 0))
1859 FIXME("options not implemented 0x%08x 0x%08lx\n", fSearchOp
, dwAdditionalFlags
);
1860 return INVALID_HANDLE_VALUE
;
1863 switch(fInfoLevelId
)
1865 case FindExInfoStandard
:
1867 WIN32_FIND_DATAW
* data
= (WIN32_FIND_DATAW
*) lpFindFileData
;
1871 data
->dwReserved0
= data
->dwReserved1
= 0x0;
1872 if (lpFileName
[0] == '\\' && lpFileName
[1] == '\\')
1874 ERR("UNC path name\n");
1875 if (!(info
= HeapAlloc( GetProcessHeap(), 0, sizeof(FIND_FIRST_INFO
)))) break;
1876 info
->u
.smb_dir
= SMB_FindFirst(lpFileName
);
1877 if(!info
->u
.smb_dir
)
1879 HeapFree(GetProcessHeap(), 0, info
);
1883 RtlInitializeCriticalSection( &info
->cs
);
1887 DOS_FULL_NAME full_name
;
1889 if (lpFileName
[0] && lpFileName
[1] == ':')
1891 /* don't allow root directories */
1892 if (!lpFileName
[2] ||
1893 ((lpFileName
[2] == '/' || lpFileName
[2] == '\\') && !lpFileName
[3]))
1895 SetLastError(ERROR_FILE_NOT_FOUND
);
1896 return INVALID_HANDLE_VALUE
;
1899 if (!DOSFS_GetFullName( lpFileName
, FALSE
, &full_name
)) break;
1900 if (!(info
= HeapAlloc( GetProcessHeap(), 0, sizeof(FIND_FIRST_INFO
)))) break;
1901 RtlInitializeCriticalSection( &info
->cs
);
1902 info
->path
= HeapAlloc( GetProcessHeap(), 0, strlen(full_name
.long_name
)+1 );
1903 strcpy( info
->path
, full_name
.long_name
);
1905 p
= strrchr( info
->path
, '/' );
1907 long_mask_len
= MultiByteToWideChar(CP_UNIXCP
, 0, p
, -1, NULL
, 0);
1908 info
->long_mask
= HeapAlloc( GetProcessHeap(), 0, long_mask_len
* sizeof(WCHAR
) );
1909 MultiByteToWideChar(CP_UNIXCP
, 0, p
, -1, info
->long_mask
, long_mask_len
);
1911 info
->drive
= full_name
.drive
;
1914 info
->u
.dos_dir
= DOSFS_OpenDir( info
->path
);
1916 if (!FindNextFileW( (HANDLE
) info
, data
))
1918 FindClose( (HANDLE
) info
);
1919 SetLastError( ERROR_FILE_NOT_FOUND
);
1922 return (HANDLE
) info
;
1926 FIXME("fInfoLevelId 0x%08x not implemented\n", fInfoLevelId
);
1928 return INVALID_HANDLE_VALUE
;
1931 /*************************************************************************
1932 * FindFirstFileA (KERNEL32.@)
1934 HANDLE WINAPI
FindFirstFileA(
1936 WIN32_FIND_DATAA
*lpFindData
)
1938 return FindFirstFileExA(lpFileName
, FindExInfoStandard
, lpFindData
,
1939 FindExSearchNameMatch
, NULL
, 0);
1942 /*************************************************************************
1943 * FindFirstFileExA (KERNEL32.@)
1945 HANDLE WINAPI
FindFirstFileExA(
1947 FINDEX_INFO_LEVELS fInfoLevelId
,
1948 LPVOID lpFindFileData
,
1949 FINDEX_SEARCH_OPS fSearchOp
,
1950 LPVOID lpSearchFilter
,
1951 DWORD dwAdditionalFlags
)
1954 WIN32_FIND_DATAA
*dataA
;
1955 WIN32_FIND_DATAW dataW
;
1956 UNICODE_STRING pathW
;
1960 SetLastError(ERROR_PATH_NOT_FOUND
);
1961 return INVALID_HANDLE_VALUE
;
1964 if (!RtlCreateUnicodeStringFromAsciiz(&pathW
, lpFileName
))
1966 SetLastError(ERROR_NOT_ENOUGH_MEMORY
);
1967 return INVALID_HANDLE_VALUE
;
1970 handle
= FindFirstFileExW(pathW
.Buffer
, fInfoLevelId
, &dataW
, fSearchOp
, lpSearchFilter
, dwAdditionalFlags
);
1971 RtlFreeUnicodeString(&pathW
);
1972 if (handle
== INVALID_HANDLE_VALUE
) return handle
;
1974 dataA
= (WIN32_FIND_DATAA
*) lpFindFileData
;
1975 dataA
->dwFileAttributes
= dataW
.dwFileAttributes
;
1976 dataA
->ftCreationTime
= dataW
.ftCreationTime
;
1977 dataA
->ftLastAccessTime
= dataW
.ftLastAccessTime
;
1978 dataA
->ftLastWriteTime
= dataW
.ftLastWriteTime
;
1979 dataA
->nFileSizeHigh
= dataW
.nFileSizeHigh
;
1980 dataA
->nFileSizeLow
= dataW
.nFileSizeLow
;
1981 WideCharToMultiByte( CP_ACP
, 0, dataW
.cFileName
, -1,
1982 dataA
->cFileName
, sizeof(dataA
->cFileName
), NULL
, NULL
);
1983 WideCharToMultiByte( CP_ACP
, 0, dataW
.cAlternateFileName
, -1,
1984 dataA
->cAlternateFileName
, sizeof(dataA
->cAlternateFileName
), NULL
, NULL
);
1988 /*************************************************************************
1989 * FindFirstFileW (KERNEL32.@)
1991 HANDLE WINAPI
FindFirstFileW( LPCWSTR lpFileName
, WIN32_FIND_DATAW
*lpFindData
)
1993 return FindFirstFileExW(lpFileName
, FindExInfoStandard
, lpFindData
,
1994 FindExSearchNameMatch
, NULL
, 0);
1997 /*************************************************************************
1998 * FindNextFileW (KERNEL32.@)
2000 BOOL WINAPI
FindNextFileW( HANDLE handle
, WIN32_FIND_DATAW
*data
)
2002 FIND_FIRST_INFO
*info
;
2004 DWORD gle
= ERROR_NO_MORE_FILES
;
2006 if (handle
== INVALID_HANDLE_VALUE
)
2008 SetLastError( ERROR_INVALID_HANDLE
);
2011 info
= (FIND_FIRST_INFO
*) handle
;
2012 RtlEnterCriticalSection( &info
->cs
);
2013 if (info
->drive
== -1)
2015 ret
= SMB_FindNext( info
->u
.smb_dir
, data
);
2018 SMB_CloseDir( info
->u
.smb_dir
);
2019 HeapFree( GetProcessHeap(), 0, info
->path
);
2023 else if (!info
->path
|| !info
->u
.dos_dir
)
2027 else if (!DOSFS_FindNextEx( info
, data
))
2029 DOSFS_CloseDir( info
->u
.dos_dir
); info
->u
.dos_dir
= NULL
;
2030 HeapFree( GetProcessHeap(), 0, info
->path
);
2032 HeapFree( GetProcessHeap(), 0, info
->long_mask
);
2033 info
->long_mask
= NULL
;
2038 RtlLeaveCriticalSection( &info
->cs
);
2039 if( !ret
) SetLastError( gle
);
2044 /*************************************************************************
2045 * FindNextFileA (KERNEL32.@)
2047 BOOL WINAPI
FindNextFileA( HANDLE handle
, WIN32_FIND_DATAA
*data
)
2049 WIN32_FIND_DATAW dataW
;
2050 if (!FindNextFileW( handle
, &dataW
)) return FALSE
;
2051 data
->dwFileAttributes
= dataW
.dwFileAttributes
;
2052 data
->ftCreationTime
= dataW
.ftCreationTime
;
2053 data
->ftLastAccessTime
= dataW
.ftLastAccessTime
;
2054 data
->ftLastWriteTime
= dataW
.ftLastWriteTime
;
2055 data
->nFileSizeHigh
= dataW
.nFileSizeHigh
;
2056 data
->nFileSizeLow
= dataW
.nFileSizeLow
;
2057 WideCharToMultiByte( CP_ACP
, 0, dataW
.cFileName
, -1,
2058 data
->cFileName
, sizeof(data
->cFileName
), NULL
, NULL
);
2059 WideCharToMultiByte( CP_ACP
, 0, dataW
.cAlternateFileName
, -1,
2060 data
->cAlternateFileName
,
2061 sizeof(data
->cAlternateFileName
), NULL
, NULL
);
2065 /*************************************************************************
2066 * FindClose (KERNEL32.@)
2068 BOOL WINAPI
FindClose( HANDLE handle
)
2070 FIND_FIRST_INFO
*info
= (FIND_FIRST_INFO
*) handle
;
2072 if (handle
== INVALID_HANDLE_VALUE
) goto error
;
2076 RtlEnterCriticalSection( &info
->cs
);
2079 if (info
->u
.dos_dir
) DOSFS_CloseDir( info
->u
.dos_dir
);
2080 if (info
->path
) HeapFree( GetProcessHeap(), 0, info
->path
);
2081 if (info
->long_mask
) HeapFree( GetProcessHeap(), 0, info
->long_mask
);
2084 __EXCEPT(page_fault
)
2086 WARN("Illegal handle %p\n", handle
);
2087 SetLastError( ERROR_INVALID_HANDLE
);
2091 if (!info
) goto error
;
2092 RtlLeaveCriticalSection( &info
->cs
);
2093 RtlDeleteCriticalSection( &info
->cs
);
2094 HeapFree(GetProcessHeap(), 0, info
);
2098 SetLastError( ERROR_INVALID_HANDLE
);
2102 /***********************************************************************
2103 * MulDiv (KERNEL32.@)
2105 * Result of multiplication and division
2106 * -1: Overflow occurred or Divisor was 0
2113 #if SIZEOF_LONG_LONG >= 8
2116 if (!nDivisor
) return -1;
2118 /* We want to deal with a positive divisor to simplify the logic. */
2121 nMultiplicand
= - nMultiplicand
;
2122 nDivisor
= -nDivisor
;
2125 /* If the result is positive, we "add" to round. else, we subtract to round. */
2126 if ( ( (nMultiplicand
< 0) && (nMultiplier
< 0) ) ||
2127 ( (nMultiplicand
>= 0) && (nMultiplier
>= 0) ) )
2128 ret
= (((long long)nMultiplicand
* nMultiplier
) + (nDivisor
/2)) / nDivisor
;
2130 ret
= (((long long)nMultiplicand
* nMultiplier
) - (nDivisor
/2)) / nDivisor
;
2132 if ((ret
> 2147483647) || (ret
< -2147483647)) return -1;
2135 if (!nDivisor
) return -1;
2137 /* We want to deal with a positive divisor to simplify the logic. */
2140 nMultiplicand
= - nMultiplicand
;
2141 nDivisor
= -nDivisor
;
2144 /* If the result is positive, we "add" to round. else, we subtract to round. */
2145 if ( ( (nMultiplicand
< 0) && (nMultiplier
< 0) ) ||
2146 ( (nMultiplicand
>= 0) && (nMultiplier
>= 0) ) )
2147 return ((nMultiplicand
* nMultiplier
) + (nDivisor
/2)) / nDivisor
;
2149 return ((nMultiplicand
* nMultiplier
) - (nDivisor
/2)) / nDivisor
;
2155 /***********************************************************************
2156 * DosDateTimeToFileTime (KERNEL32.@)
2158 BOOL WINAPI
DosDateTimeToFileTime( WORD fatdate
, WORD fattime
, LPFILETIME ft
)
2163 time_t time1
, time2
;
2166 newtm
.tm_sec
= (fattime
& 0x1f) * 2;
2167 newtm
.tm_min
= (fattime
>> 5) & 0x3f;
2168 newtm
.tm_hour
= (fattime
>> 11);
2169 newtm
.tm_mday
= (fatdate
& 0x1f);
2170 newtm
.tm_mon
= ((fatdate
>> 5) & 0x0f) - 1;
2171 newtm
.tm_year
= (fatdate
>> 9) + 80;
2173 RtlSecondsSince1970ToTime( timegm(&newtm
), (LARGE_INTEGER
*)ft
);
2175 time1
= mktime(&newtm
);
2176 gtm
= gmtime(&time1
);
2177 time2
= mktime(gtm
);
2178 RtlSecondsSince1970ToTime( 2*time1
-time2
, (LARGE_INTEGER
*)ft
);
2184 /***********************************************************************
2185 * FileTimeToDosDateTime (KERNEL32.@)
2187 BOOL WINAPI
FileTimeToDosDateTime( const FILETIME
*ft
, LPWORD fatdate
,
2195 li
.s
.LowPart
= ft
->dwLowDateTime
;
2196 li
.s
.HighPart
= ft
->dwHighDateTime
;
2197 RtlTimeToSecondsSince1970( &li
, &t
);
2199 tm
= gmtime( &unixtime
);
2201 *fattime
= (tm
->tm_hour
<< 11) + (tm
->tm_min
<< 5) + (tm
->tm_sec
/ 2);
2203 *fatdate
= ((tm
->tm_year
- 80) << 9) + ((tm
->tm_mon
+ 1) << 5)
2209 /***********************************************************************
2210 * QueryDosDeviceA (KERNEL32.@)
2212 * returns array of strings terminated by \0, terminated by \0
2214 DWORD WINAPI
QueryDosDeviceA(LPCSTR devname
,LPSTR target
,DWORD bufsize
)
2216 DWORD ret
= 0, retW
;
2217 LPWSTR targetW
= (LPWSTR
)HeapAlloc(GetProcessHeap(),0,
2218 bufsize
* sizeof(WCHAR
));
2219 UNICODE_STRING devnameW
;
2221 if(devname
) RtlCreateUnicodeStringFromAsciiz(&devnameW
, devname
);
2222 else devnameW
.Buffer
= NULL
;
2224 retW
= QueryDosDeviceW(devnameW
.Buffer
, targetW
, bufsize
);
2226 ret
= WideCharToMultiByte(CP_ACP
, 0, targetW
, retW
, target
,
2227 bufsize
, NULL
, NULL
);
2229 RtlFreeUnicodeString(&devnameW
);
2230 if (targetW
) HeapFree(GetProcessHeap(),0,targetW
);
2235 /***********************************************************************
2236 * QueryDosDeviceW (KERNEL32.@)
2238 * returns array of strings terminated by \0, terminated by \0
2241 * - Win9x returns for all calls ERROR_INVALID_PARAMETER
2242 * - the returned devices for devname == NULL is far from complete
2243 * - its not checked that the returned device exist
2245 DWORD WINAPI
QueryDosDeviceW(LPCWSTR devname
,LPWSTR target
,DWORD bufsize
)
2247 const WCHAR
*pDev
, *pName
, *pNum
= NULL
;
2251 TRACE("(%s,...)\n", debugstr_w(devname
));
2253 /* return known MSDOS devices */
2256 static const WCHAR devices
[][5] = {{'A','U','X',0},
2257 {'C','O','M','1',0},
2258 {'C','O','M','2',0},
2259 {'L','P','T','1',0},
2261 for(i
=0; (i
< (sizeof(devices
)/sizeof(devices
[0]))); i
++) {
2262 DWORD len
= strlenW(devices
[i
]);
2263 if(target
&& (bufsize
>= ret
+ len
+ 2)) {
2264 strcpyW(target
+ret
, devices
[i
]);
2267 /* in this case WinXP returns 0 */
2268 FIXME("function return is wrong for WinXP!\n");
2269 SetLastError(ERROR_INSUFFICIENT_BUFFER
);
2273 /* append drives here */
2274 if(target
&& bufsize
> 0) target
[ret
++] = 0;
2275 FIXME("Returned list is not complete\n");
2278 /* In theory all that are possible and have been defined.
2279 * Now just those below, since mirc uses it to check for special files.
2281 * (It is more complex, and supports netmounted stuff, and \\.\ stuff,
2282 * but currently we just ignore that.)
2284 if (!strcmpiW(devname
, auxW
)) {
2289 } else if (!strcmpiW(devname
, nulW
)) {
2292 } else if (!strncmpiW(devname
, comW
, strlenW(comW
))) {
2295 pNum
= devname
+ strlenW(comW
);
2296 for(numsiz
=0; isdigitW(*(pNum
+numsiz
)); numsiz
++);
2297 if(*(pNum
+ numsiz
)) {
2298 SetLastError(ERROR_FILE_NOT_FOUND
);
2301 } else if (!strncmpiW(devname
, lptW
, strlenW(lptW
))) {
2304 pNum
= devname
+ strlenW(lptW
);
2305 for(numsiz
=0; isdigitW(*(pNum
+numsiz
)); numsiz
++);
2306 if(*(pNum
+ numsiz
)) {
2307 SetLastError(ERROR_FILE_NOT_FOUND
);
2311 /* This might be a DOS device we do not handle yet ... */
2312 FIXME("(%s) not detected as DOS device!\n",debugstr_w(devname
));
2314 /* Win9x set the error ERROR_INVALID_PARAMETER */
2315 SetLastError(ERROR_FILE_NOT_FOUND
);
2318 FIXME("device %s may not exist on this computer\n", debugstr_w(devname
));
2320 ret
= strlenW(pDev
) + strlenW(pName
) + numsiz
+ 2;
2321 if (ret
> bufsize
) ret
= 0;
2322 if (target
&& ret
) {
2323 strcpyW(target
,pDev
);
2324 strcatW(target
,pName
);
2325 if (pNum
) strcatW(target
,pNum
);