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"
59 #include "wine/server.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 */
159 static WINE_EXCEPTION_FILTER(page_fault)
161 if (GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION)
162 return EXCEPTION_EXECUTE_HANDLER;
163 return EXCEPTION_CONTINUE_SEARCH;
167 /***********************************************************************
170 * Return 1 if Unix file 'name' is also a valid MS-DOS name
171 * (i.e. contains only valid DOS chars, lower-case only, fits in 8.3 format).
172 * File name can be terminated by '\0', '\\' or '/'.
174 static int DOSFS_ValidDOSName( LPCWSTR name, int ignore_case )
176 static const char invalid_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" INVALID_DOS_CHARS;
177 const WCHAR *p = name;
178 const char *invalid = ignore_case ? (invalid_chars + 26) : invalid_chars;
183 /* Check for "." and ".." */
186 /* All other names beginning with '.' are invalid */
187 return (IS_END_OF_NAME(*p));
189 while (!IS_END_OF_NAME(*p))
191 if (*p < 256 && strchr( invalid, (char)*p )) return 0; /* Invalid char */
192 if (*p == '.') break; /* Start of the extension */
193 if (++len > 8) return 0; /* Name too long */
196 if (*p != '.') return 1; /* End of name */
198 if (IS_END_OF_NAME(*p)) return 0; /* Empty extension not allowed */
200 while (!IS_END_OF_NAME(*p))
202 if (*p < 256 && strchr( invalid, (char)*p )) return 0; /* Invalid char */
203 if (*p == '.') return 0; /* Second extension not allowed */
204 if (++len > 3) return 0; /* Extension too long */
211 /***********************************************************************
212 * DOSFS_ToDosFCBFormat
214 * Convert a file name to DOS FCB format (8+3 chars, padded with blanks),
215 * expanding wild cards and converting to upper-case in the process.
216 * File name can be terminated by '\0', '\\' or '/'.
217 * Return FALSE if the name is not a valid DOS name.
218 * 'buffer' must be at least 12 characters long.
220 BOOL DOSFS_ToDosFCBFormat( LPCWSTR name, LPWSTR buffer )
222 static const char invalid_chars[] = INVALID_DOS_CHARS;
226 /* Check for "." and ".." */
231 for(i = 1; i < 11; i++) buffer[i] = ' ';
238 return (!*p || (*p == '/') || (*p == '\\'));
241 for (i = 0; i < 8; i++)
258 if (*p < 256 && strchr( invalid_chars, (char)*p )) return FALSE;
259 buffer[i] = toupperW(*p);
267 /* Skip all chars after wildcard up to first dot */
268 while (*p && (*p != '/') && (*p != '\\') && (*p != '.')) p++;
272 /* Check if name too long */
273 if (*p && (*p != '/') && (*p != '\\') && (*p != '.')) return FALSE;
275 if (*p == '.') p++; /* Skip dot */
277 for (i = 8; i < 11; i++)
287 return FALSE; /* Second extension not allowed */
295 if (*p < 256 && strchr( invalid_chars, (char)*p )) return FALSE;
296 buffer[i] = toupperW(*p);
303 /* at most 3 character of the extension are processed
304 * is something behind this ?
306 while (*p == '*' || *p == ' ') p++; /* skip wildcards and spaces */
307 return IS_END_OF_NAME(*p);
311 /***********************************************************************
312 * DOSFS_ToDosDTAFormat
314 * Convert a file name from FCB to DTA format (name.ext, null-terminated)
315 * converting to upper-case in the process.
316 * File name can be terminated by '\0', '\\' or '/'.
317 * 'buffer' must be at least 13 characters long.
319 static void DOSFS_ToDosDTAFormat( LPCWSTR name, LPWSTR buffer )
323 memcpy( buffer, name, 8 * sizeof(WCHAR) );
325 while ((p > buffer) && (p[-1] == ' ')) p--;
327 memcpy( p, name + 8, 3 * sizeof(WCHAR) );
329 while (p[-1] == ' ') p--;
330 if (p[-1] == '.') p--;
335 /***********************************************************************
338 * Check a DOS file name against a mask (both in FCB format).
340 static int DOSFS_MatchShort( LPCWSTR mask, LPCWSTR name )
343 for (i = 11; i > 0; i--, mask++, name++)
344 if ((*mask != '?') && (*mask != *name)) return 0;
349 /***********************************************************************
352 * Check a long file name against a mask.
354 * Tests (done in W95 DOS shell - case insensitive):
355 * *.txt test1.test.txt *
357 * *.t??????.t* test1.ta.tornado.txt *
358 * *tornado* test1.ta.tornado.txt *
359 * t*t test1.ta.tornado.txt *
361 * ?est??? test1.txt -
362 * *test1.txt* test1.txt *
363 * h?l?o*t.dat hellothisisatest.dat *
365 static int DOSFS_MatchLong( LPCWSTR mask, LPCWSTR name, int case_sensitive )
367 LPCWSTR lastjoker = NULL;
368 LPCWSTR next_to_retry = NULL;
369 static const WCHAR asterisk_dot_asterisk[] = {'*','.','*',0};
371 TRACE("(%s, %s, %x)\n", debugstr_w(mask), debugstr_w(name), case_sensitive);
373 if (!strcmpW( mask, asterisk_dot_asterisk )) return 1;
374 while (*name && *mask)
379 while (*mask == '*') mask++; /* Skip consecutive '*' */
381 if (!*mask) return 1; /* end of mask is all '*', so match */
383 /* skip to the next match after the joker(s) */
384 if (case_sensitive) while (*name && (*name != *mask)) name++;
385 else while (*name && (toupperW(*name) != toupperW(*mask))) name++;
388 next_to_retry = name;
390 else if (*mask != '?')
395 if (*mask != *name) mismatch = 1;
399 if (toupperW(*mask) != toupperW(*name)) mismatch = 1;
413 else /* mismatch ! */
415 if (lastjoker) /* we had an '*', so we can try unlimitedly */
419 /* this scan sequence was a mismatch, so restart
420 * 1 char after the first char we checked last time */
422 name = next_to_retry;
425 return 0; /* bad luck */
434 while ((*mask == '.') || (*mask == '*'))
435 mask++; /* Ignore trailing '.' or '*' in mask */
436 return (!*name && !*mask);
440 /***********************************************************************
443 * Used to construct an array of filenames in DOSFS_OpenDir
445 static BOOL DOSFS_AddDirEntry(DOS_DIR **dir, LPCWSTR name, LPCWSTR dosname)
447 int extra1 = strlenW(name) + 1;
448 int extra2 = strlenW(dosname) + 1;
450 /* if we need more, at minimum double the size */
451 if( (extra1 + extra2 + (*dir)->used) > (*dir)->size)
453 int more = (*dir)->size;
456 if(more<(extra1+extra2))
457 more = extra1+extra2;
459 t = HeapReAlloc(GetProcessHeap(), 0, *dir, sizeof(**dir) +
460 ((*dir)->size + more)*sizeof(WCHAR) );
463 SetLastError( ERROR_NOT_ENOUGH_MEMORY );
464 ERR("Out of memory caching directory structure %d %d %d\n",
465 (*dir)->size, more, (*dir)->used);
469 (*dir)->size += more;
472 /* at this point, the dir structure is big enough to hold these names */
473 strcpyW(&(*dir)->names[(*dir)->used], name);
474 (*dir)->used += extra1;
475 strcpyW(&(*dir)->names[(*dir)->used], dosname);
476 (*dir)->used += extra2;
482 /***********************************************************************
485 static BOOL DOSFS_OpenDir_VFAT(DOS_DIR **dir, const char *unix_path)
487 #ifdef VFAT_IOCTL_READDIR_BOTH
489 int fd = open( unix_path, O_RDONLY|O_DIRECTORY );
492 /* Check if the VFAT ioctl is supported on this directory */
499 WCHAR long_name[MAX_PATH];
500 WCHAR short_name[12];
502 r = (ioctl( fd, VFAT_IOCTL_READDIR_BOTH, (long)de ) != -1);
507 MultiByteToWideChar(CP_UNIXCP, 0, de[0].d_name, -1, long_name, MAX_PATH);
508 if (!DOSFS_ToDosFCBFormat( long_name, short_name ))
509 short_name[0] = '\0';
511 MultiByteToWideChar(CP_UNIXCP, 0, de[1].d_name, -1, long_name, MAX_PATH);
513 MultiByteToWideChar(CP_UNIXCP, 0, de[0].d_name, -1, long_name, MAX_PATH);
514 r = DOSFS_AddDirEntry(dir, long_name, short_name );
520 static const WCHAR empty_strW[] = { 0 };
521 DOSFS_AddDirEntry(dir, empty_strW, empty_strW);
527 #endif /* VFAT_IOCTL_READDIR_BOTH */
531 /***********************************************************************
532 * DOSFS_OpenDir_Normal
534 * Now use the standard opendir/readdir interface
536 static BOOL DOSFS_OpenDir_Normal( DOS_DIR **dir, const char *unix_path )
538 DIR *unixdir = opendir( unix_path );
540 static const WCHAR empty_strW[] = { 0 };
546 WCHAR long_name[MAX_PATH];
547 struct dirent *de = readdir(unixdir);
551 MultiByteToWideChar(CP_UNIXCP, 0, de->d_name, -1, long_name, MAX_PATH);
552 r = DOSFS_AddDirEntry(dir, long_name, empty_strW);
557 DOSFS_AddDirEntry(dir, empty_strW, empty_strW);
562 /***********************************************************************
565 static DOS_DIR *DOSFS_OpenDir( const char *unix_path )
567 const int init_size = 0x100;
568 DOS_DIR *dir = HeapAlloc( GetProcessHeap(), 0, sizeof(*dir) + init_size*sizeof (WCHAR));
571 TRACE("%s\n",debugstr_a(unix_path));
575 SetLastError( ERROR_NOT_ENOUGH_MEMORY );
579 dir->size = init_size;
581 /* Treat empty path as root directory. This simplifies path split into
582 directory and mask in several other places */
583 if (!*unix_path) unix_path = "/";
585 r = DOSFS_OpenDir_VFAT( &dir, unix_path);
588 r = DOSFS_OpenDir_Normal( &dir, unix_path);
592 HeapFree(GetProcessHeap(), 0, dir);
601 /***********************************************************************
604 static void DOSFS_CloseDir( DOS_DIR *dir )
606 HeapFree( GetProcessHeap(), 0, dir );
610 /***********************************************************************
613 static BOOL DOSFS_ReadDir( DOS_DIR *dir, LPCWSTR *long_name,
614 LPCWSTR *short_name )
621 /* the long pathname is first */
622 ln = &dir->names[dir->used];
627 dir->used += (strlenW(ln) + 1);
629 /* followed by the short path name */
630 sn = &dir->names[dir->used];
635 dir->used += (strlenW(sn) + 1);
641 /***********************************************************************
644 * Transform a Unix file name into a hashed DOS name. If the name is a valid
645 * DOS name, it is converted to upper-case; otherwise it is replaced by a
646 * hashed version that fits in 8.3 format.
647 * File name can be terminated by '\0', '\\' or '/'.
648 * 'buffer' must be at least 13 characters long.
650 static void DOSFS_Hash( LPCWSTR name, LPWSTR buffer, BOOL dir_format,
653 static const char invalid_chars[] = INVALID_DOS_CHARS "~.";
654 static const char hash_chars[32] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345";
663 for(i = 0; i < 11; i++) buffer[i] = ' ';
667 if (DOSFS_ValidDOSName( name, ignore_case ))
669 /* Check for '.' and '..' */
673 if (!dir_format) buffer[1] = buffer[2] = '\0';
674 if (name[1] == '.') buffer[1] = '.';
678 /* Simply copy the name, converting to uppercase */
680 for (dst = buffer; !IS_END_OF_NAME(*name) && (*name != '.'); name++)
681 *dst++ = toupperW(*name);
684 if (dir_format) dst = buffer + 8;
686 for (name++; !IS_END_OF_NAME(*name); name++)
687 *dst++ = toupperW(*name);
689 if (!dir_format) *dst = '\0';
693 /* Compute the hash code of the file name */
694 /* If you know something about hash functions, feel free to */
695 /* insert a better algorithm here... */
698 for (p = name, hash = 0xbeef; !IS_END_OF_NAME(p[1]); p++)
699 hash = (hash<<3) ^ (hash>>5) ^ tolowerW(*p) ^ (tolowerW(p[1]) << 8);
700 hash = (hash<<3) ^ (hash>>5) ^ tolowerW(*p); /* Last character */
704 for (p = name, hash = 0xbeef; !IS_END_OF_NAME(p[1]); p++)
705 hash = (hash << 3) ^ (hash >> 5) ^ *p ^ (p[1] << 8);
706 hash = (hash << 3) ^ (hash >> 5) ^ *p; /* Last character */
709 /* Find last dot for start of the extension */
710 for (p = name+1, ext = NULL; !IS_END_OF_NAME(*p); p++)
711 if (*p == '.') ext = p;
712 if (ext && IS_END_OF_NAME(ext[1]))
713 ext = NULL; /* Empty extension ignored */
715 /* Copy first 4 chars, replacing invalid chars with '_' */
716 for (i = 4, p = name, dst = buffer; i > 0; i--, p++)
718 if (IS_END_OF_NAME(*p) || (p == ext)) break;
719 *dst++ = (*p < 256 && strchr( invalid_chars, (char)*p )) ? '_' : toupperW(*p);
721 /* Pad to 5 chars with '~' */
722 while (i-- >= 0) *dst++ = '~';
724 /* Insert hash code converted to 3 ASCII chars */
725 *dst++ = hash_chars[(hash >> 10) & 0x1f];
726 *dst++ = hash_chars[(hash >> 5) & 0x1f];
727 *dst++ = hash_chars[hash & 0x1f];
729 /* Copy the first 3 chars of the extension (if any) */
732 if (!dir_format) *dst++ = '.';
733 for (i = 3, ext++; (i > 0) && !IS_END_OF_NAME(*ext); i--, ext++)
734 *dst++ = (*ext < 256 && strchr( invalid_chars, (char)*ext )) ? '_' : toupperW(*ext);
736 if (!dir_format) *dst = '\0';
740 /***********************************************************************
743 * Find the Unix file name in a given directory that corresponds to
744 * a file name (either in Unix or DOS format).
745 * File name can be terminated by '\0', '\\' or '/'.
746 * Return TRUE if OK, FALSE if no file name matches.
748 * 'long_buf' must be at least 'long_len' characters long. If the long name
749 * turns out to be larger than that, the function returns FALSE.
750 * 'short_buf' must be at least 13 characters long.
752 BOOL DOSFS_FindUnixName( const DOS_FULL_NAME *path, LPCWSTR name, char *long_buf,
753 INT long_len, LPWSTR short_buf, BOOL ignore_case)
756 LPCWSTR long_name, short_name;
757 WCHAR dos_name[12], tmp_buf[13];
760 LPCWSTR p = strchrW( name, '/' );
761 int len = p ? (int)(p - name) : strlenW(name);
762 if ((p = strchrW( name, '\\' ))) len = min( (int)(p - name), len );
763 /* Ignore trailing dots and spaces */
764 while (len > 1 && (name[len-1] == '.' || name[len-1] == ' ')) len--;
765 if (long_len < len + 1) return FALSE;
767 TRACE("%s,%s\n", path->long_name, debugstr_w(name) );
769 if (!DOSFS_ToDosFCBFormat( name, dos_name )) dos_name[0] = '\0';
771 if (!(dir = DOSFS_OpenDir( path->long_name )))
773 WARN("(%s,%s): can't open dir: %s\n",
774 path->long_name, debugstr_w(name), strerror(errno) );
778 while ((ret = DOSFS_ReadDir( dir, &long_name, &short_name )))
780 /* Check against Unix name */
781 if (len == strlenW(long_name))
785 if (!strncmpW( long_name, name, len )) break;
789 if (!strncmpiW( long_name, name, len )) break;
794 /* Check against hashed DOS name */
797 DOSFS_Hash( long_name, tmp_buf, TRUE, ignore_case );
798 short_name = tmp_buf;
800 if (!strcmpW( dos_name, short_name )) break;
805 if (long_buf) WideCharToMultiByte(CP_UNIXCP, 0, long_name, -1, long_buf, long_len, NULL, NULL);
809 DOSFS_ToDosDTAFormat( short_name, short_buf );
811 DOSFS_Hash( long_name, short_buf, FALSE, ignore_case );
813 TRACE("(%s,%s) -> %s (%s)\n", path->long_name, debugstr_w(name),
814 debugstr_w(long_name), short_buf ? debugstr_w(short_buf) : "***");
817 WARN("%s not found in '%s'\n", debugstr_w(name), path->long_name);
818 DOSFS_CloseDir( dir );
823 /***********************************************************************
826 * Check if a DOS file name represents a DOS device and return the device.
828 const DOS_DEVICE *DOSFS_GetDevice( LPCWSTR name )
833 if (!name) return NULL; /* if wine_server_handle_to_fd was used */
834 if (name[0] && (name[1] == ':')) name += 2;
835 if ((p = strrchrW( name, '/' ))) name = p + 1;
836 if ((p = strrchrW( name, '\\' ))) name = p + 1;
837 for (i = 0; i < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0]); i++)
839 const WCHAR *dev = DOSFS_Devices[i].name;
840 if (!strncmpiW( dev, name, strlenW(dev) ))
842 p = name + strlenW( dev );
843 if (!*p || (*p == '.') || (*p == ':')) return &DOSFS_Devices[i];
850 /***********************************************************************
851 * DOSFS_GetDeviceByHandle
853 const DOS_DEVICE *DOSFS_GetDeviceByHandle( HANDLE hFile )
855 const DOS_DEVICE *ret = NULL;
856 SERVER_START_REQ( get_device_id )
859 if (!wine_server_call( req ))
861 if ((reply->id >= 0) &&
862 (reply->id < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0])))
863 ret = &DOSFS_Devices[reply->id];
871 /**************************************************************************
872 * DOSFS_CreateCommPort
874 static HANDLE DOSFS_CreateCommPort(LPCWSTR name, DWORD access, DWORD attributes, LPSECURITY_ATTRIBUTES sa)
879 OBJECT_ATTRIBUTES attr;
880 UNICODE_STRING nameW;
885 static const WCHAR serialportsW[] = {'M','a','c','h','i','n','e','\\',
886 'S','o','f','t','w','a','r','e','\\',
887 'W','i','n','e','\\','W','i','n','e','\\',
888 'C','o','n','f','i','g','\\',
889 'S','e','r','i','a','l','P','o','r','t','s',0};
891 TRACE_(file)("%s %lx %lx\n", debugstr_w(name), access, attributes);
893 attr.Length = sizeof(attr);
894 attr.RootDirectory = 0;
895 attr.ObjectName = &nameW;
897 attr.SecurityDescriptor = NULL;
898 attr.SecurityQualityOfService = NULL;
899 RtlInitUnicodeString( &nameW, serialportsW );
901 if (NtOpenKey( &hkey, KEY_ALL_ACCESS, &attr )) return 0;
903 RtlInitUnicodeString( &nameW, name );
904 if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
905 devnameW = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
911 if (!devnameW) return 0;
912 WideCharToMultiByte(CP_ACP, 0, devnameW, -1, devname, sizeof(devname), NULL, NULL);
914 TRACE("opening %s as %s\n", devname, debugstr_w(name));
916 SERVER_START_REQ( create_serial )
918 req->access = access;
919 req->inherit = (sa && (sa->nLength>=sizeof(*sa)) && sa->bInheritHandle);
920 req->attributes = attributes;
921 req->sharing = FILE_SHARE_READ|FILE_SHARE_WRITE;
922 wine_server_add_data( req, devname, strlen(devname) );
924 wine_server_call_err( req );
930 ERR("Couldn't open device '%s' ! (check permissions)\n",devname);
932 TRACE("return %p\n", ret );
936 /***********************************************************************
939 * Open a DOS device. This might not map 1:1 into the UNIX device concept.
940 * Returns 0 on failure.
942 HANDLE DOSFS_OpenDevice( LPCWSTR name, DWORD access, DWORD attributes, LPSECURITY_ATTRIBUTES sa )
948 if (name[0] && (name[1] == ':')) name += 2;
949 if ((p = strrchrW( name, '/' ))) name = p + 1;
950 if ((p = strrchrW( name, '\\' ))) name = p + 1;
951 for (i = 0; i < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0]); i++)
953 const WCHAR *dev = DOSFS_Devices[i].name;
954 if (!strncmpiW( dev, name, strlenW(dev) ))
956 p = name + strlenW( dev );
957 if (!*p || (*p == '.') || (*p == ':')) {
958 static const WCHAR nulW[] = {'N','U','L',0};
959 static const WCHAR conW[] = {'C','O','N',0};
960 static const WCHAR scsimgrW[] = {'S','C','S','I','M','G','R','$',0};
961 static const WCHAR hpscanW[] = {'H','P','S','C','A','N',0};
962 static const WCHAR emmxxxx0W[] = {'E','M','M','X','X','X','X','0',0};
964 if (!strcmpiW(DOSFS_Devices[i].name, nulW))
965 return FILE_CreateFile( "/dev/null", access,
966 FILE_SHARE_READ|FILE_SHARE_WRITE, sa,
967 OPEN_EXISTING, 0, 0, TRUE, DRIVE_UNKNOWN );
968 if (!strcmpiW(DOSFS_Devices[i].name, conW)) {
970 switch (access & (GENERIC_READ|GENERIC_WRITE)) {
972 to_dup = GetStdHandle( STD_INPUT_HANDLE );
975 to_dup = GetStdHandle( STD_OUTPUT_HANDLE );
978 FIXME("can't open CON read/write\n");
981 if (!DuplicateHandle( GetCurrentProcess(), to_dup, GetCurrentProcess(),
983 sa && (sa->nLength>=sizeof(*sa)) && sa->bInheritHandle,
984 DUPLICATE_SAME_ACCESS ))
988 if (!strcmpiW(DOSFS_Devices[i].name, scsimgrW) ||
989 !strcmpiW(DOSFS_Devices[i].name, hpscanW) ||
990 !strcmpiW(DOSFS_Devices[i].name, emmxxxx0W))
992 return FILE_CreateDevice( i, access, sa );
995 if( (handle=DOSFS_CreateCommPort(DOSFS_Devices[i].name,access,attributes,sa)) )
997 FIXME("device open %s not supported (yet)\n", debugstr_w(DOSFS_Devices[i].name));
1006 /***********************************************************************
1007 * DOSFS_GetPathDrive
1009 * Get the drive specified by a given path name (DOS or Unix format).
1011 static int DOSFS_GetPathDrive( LPCWSTR *name )
1016 if (*p && (p[1] == ':'))
1018 drive = toupperW(*p) - 'A';
1021 else if (*p == '/') /* Absolute Unix path? */
1023 if ((drive = DRIVE_FindDriveRootW( name )) == -1)
1025 MESSAGE("Warning: %s not accessible from a configured DOS drive\n", debugstr_w(*name) );
1026 /* Assume it really was a DOS name */
1027 drive = DRIVE_GetCurrentDrive();
1030 else drive = DRIVE_GetCurrentDrive();
1032 if (!DRIVE_IsValid(drive))
1034 SetLastError( ERROR_INVALID_DRIVE );
1041 /***********************************************************************
1044 * Convert a file name (DOS or mixed DOS/Unix format) to a valid
1045 * Unix name / short DOS name pair.
1046 * Return FALSE if one of the path components does not exist. The last path
1047 * component is only checked if 'check_last' is non-zero.
1048 * The buffers pointed to by 'long_buf' and 'short_buf' must be
1049 * at least MAX_PATHNAME_LEN long.
1051 BOOL DOSFS_GetFullName( LPCWSTR name, BOOL check_last, DOS_FULL_NAME *full )
1057 static const WCHAR driveA_rootW[] = {'A',':','\\',0};
1058 static const WCHAR dos_rootW[] = {'\\',0};
1060 TRACE("%s (last=%d)\n", debugstr_w(name), check_last );
1062 if ((!*name) || (*name=='\n'))
1063 { /* error code for Win98 */
1064 SetLastError(ERROR_BAD_PATHNAME);
1068 if ((full->drive = DOSFS_GetPathDrive( &name )) == -1) return FALSE;
1069 flags = DRIVE_GetFlags( full->drive );
1071 lstrcpynA( full->long_name, DRIVE_GetRoot( full->drive ),
1072 sizeof(full->long_name) );
1073 if (full->long_name[1]) root = full->long_name + strlen(full->long_name);
1074 else root = full->long_name; /* root directory */
1076 strcpyW( full->short_name, driveA_rootW );
1077 full->short_name[0] += full->drive;
1079 if ((*name == '\\') || (*name == '/')) /* Absolute path */
1081 while ((*name == '\\') || (*name == '/')) name++;
1083 else /* Relative path */
1085 lstrcpynA( root + 1, DRIVE_GetUnixCwd( full->drive ),
1086 sizeof(full->long_name) - (root - full->long_name) - 1 );
1087 if (root[1]) *root = '/';
1088 lstrcpynW( full->short_name + 3, DRIVE_GetDosCwd( full->drive ),
1089 sizeof(full->short_name)/sizeof(full->short_name[0]) - 3 );
1092 p_l = full->long_name[1] ? full->long_name + strlen(full->long_name)
1094 p_s = full->short_name[3] ? full->short_name + strlenW(full->short_name)
1095 : full->short_name + 2;
1098 while (*name && found)
1100 /* Check for '.' and '..' */
1104 if (IS_END_OF_NAME(name[1]))
1107 while ((*name == '\\') || (*name == '/')) name++;
1110 else if ((name[1] == '.') && IS_END_OF_NAME(name[2]))
1113 while ((*name == '\\') || (*name == '/')) name++;
1114 while ((p_l > root) && (*p_l != '/')) p_l--;
1115 while ((p_s > full->short_name + 2) && (*p_s != '\\')) p_s--;
1116 *p_l = *p_s = '\0'; /* Remove trailing separator */
1121 /* Make sure buffers are large enough */
1123 if ((p_s >= full->short_name + sizeof(full->short_name)/sizeof(full->short_name[0]) - 14) ||
1124 (p_l >= full->long_name + sizeof(full->long_name) - 1))
1126 SetLastError( ERROR_PATH_NOT_FOUND );
1130 /* Get the long and short name matching the file name */
1132 if ((found = DOSFS_FindUnixName( full, name, p_l + 1,
1133 sizeof(full->long_name) - (p_l - full->long_name) - 1,
1134 p_s + 1, !(flags & DRIVE_CASE_SENSITIVE) )))
1139 p_s += strlenW(p_s);
1140 while (!IS_END_OF_NAME(*name)) name++;
1142 else if (!check_last)
1146 while (!IS_END_OF_NAME(*name) &&
1147 (p_s < full->short_name + sizeof(full->short_name)/sizeof(full->short_name[0]) - 1) &&
1148 (p_l < full->long_name + sizeof(full->long_name) - 1))
1151 *p_s++ = tolowerW(*name);
1152 /* If the drive is case-sensitive we want to create new */
1153 /* files in lower-case otherwise we can't reopen them */
1154 /* under the same short name. */
1155 if (flags & DRIVE_CASE_SENSITIVE) wch = tolowerW(*name);
1157 p_l += WideCharToMultiByte(CP_UNIXCP, 0, &wch, 1, p_l, 2, NULL, NULL);
1160 /* Ignore trailing dots and spaces */
1161 while(p_l[-1] == '.' || p_l[-1] == ' ') {
1168 while ((*name == '\\') || (*name == '/')) name++;
1175 SetLastError( ERROR_FILE_NOT_FOUND );
1178 if (*name) /* Not last */
1180 SetLastError( ERROR_PATH_NOT_FOUND );
1184 if (!full->long_name[0]) strcpy( full->long_name, "/" );
1185 if (!full->short_name[2]) strcpyW( full->short_name + 2, dos_rootW );
1186 TRACE("returning %s = %s\n", full->long_name, debugstr_w(full->short_name) );
1191 /***********************************************************************
1192 * GetShortPathNameW (KERNEL32.@)
1196 * longpath=NULL: LastError=ERROR_INVALID_PARAMETER, ret=0
1197 * longpath="" or invalid: LastError=ERROR_BAD_PATHNAME, ret=0
1199 * more observations ( with NT 3.51 (WinDD) ):
1200 * longpath <= 8.3 -> just copy longpath to shortpath
1202 * a) file does not exist -> return 0, LastError = ERROR_FILE_NOT_FOUND
1203 * b) file does exist -> set the short filename.
1204 * - trailing slashes are reproduced in the short name, even if the
1205 * file is not a directory
1206 * - the absolute/relative path of the short name is reproduced like found
1208 * - longpath and shortpath may have the same address
1209 * Peter Ganten, 1999
1211 DWORD WINAPI GetShortPathNameW( LPCWSTR longpath, LPWSTR shortpath, DWORD shortlen )
1213 DOS_FULL_NAME full_name;
1214 WCHAR tmpshortpath[MAX_PATHNAME_LEN];
1216 DWORD sp = 0, lp = 0;
1220 BOOL unixabsolute = *longpath == '/';
1222 TRACE("%s\n", debugstr_w(longpath));
1225 SetLastError(ERROR_INVALID_PARAMETER);
1229 SetLastError(ERROR_BAD_PATHNAME);
1233 /* check for drive letter */
1234 if (!unixabsolute && longpath[1] == ':' ) {
1235 tmpshortpath[0] = longpath[0];
1236 tmpshortpath[1] = ':';
1240 if ( ( drive = DOSFS_GetPathDrive ( &longpath )) == -1 ) return 0;
1241 flags = DRIVE_GetFlags ( drive );
1243 if (unixabsolute && drive != DRIVE_GetCurrentDrive()) {
1244 tmpshortpath[0] = drive + 'A';
1245 tmpshortpath[1] = ':';
1249 while ( longpath[lp] ) {
1251 /* check for path delimiters and reproduce them */
1252 if ( longpath[lp] == '\\' || longpath[lp] == '/' ) {
1253 if (!sp || tmpshortpath[sp-1]!= '\\')
1255 /* strip double "\\" */
1256 tmpshortpath[sp] = '\\';
1259 tmpshortpath[sp]=0;/*terminate string*/
1265 for(p = longpath + lp; *p && *p != '/' && *p != '\\'; p++)
1267 lstrcpynW(tmpshortpath + sp, longpath + lp, tmplen + 1);
1269 /* Check, if the current element is a valid dos name */
1270 if ( DOSFS_ValidDOSName ( longpath + lp, !(flags & DRIVE_CASE_SENSITIVE) ) ) {
1276 /* Check if the file exists and use the existing file name */
1277 if ( DOSFS_GetFullName ( tmpshortpath, TRUE, &full_name ) ) {
1278 strcpyW(tmpshortpath + sp, strrchrW(full_name.short_name, '\\') + 1);
1279 sp += strlenW(tmpshortpath + sp);
1284 TRACE("not found!\n" );
1285 SetLastError ( ERROR_FILE_NOT_FOUND );
1288 tmpshortpath[sp] = 0;
1290 tmplen = strlenW(tmpshortpath) + 1;
1291 if (tmplen <= shortlen)
1293 strcpyW(shortpath, tmpshortpath);
1294 TRACE("returning %s\n", debugstr_w(shortpath));
1295 tmplen--; /* length without 0 */
1302 /***********************************************************************
1303 * GetShortPathNameA (KERNEL32.@)
1305 DWORD WINAPI GetShortPathNameA( LPCSTR longpath, LPSTR shortpath, DWORD shortlen )
1307 UNICODE_STRING longpathW;
1308 WCHAR shortpathW[MAX_PATH];
1313 SetLastError(ERROR_INVALID_PARAMETER);
1317 TRACE("%s\n", debugstr_a(longpath));
1319 if (!RtlCreateUnicodeStringFromAsciiz(&longpathW, longpath))
1321 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
1325 retW = GetShortPathNameW(longpathW.Buffer, shortpathW, MAX_PATH);
1329 else if (retW > MAX_PATH)
1331 SetLastError(ERROR_FILENAME_EXCED_RANGE);
1336 ret = WideCharToMultiByte(CP_ACP, 0, shortpathW, -1, NULL, 0, NULL, NULL);
1337 if (ret <= shortlen)
1339 WideCharToMultiByte(CP_ACP, 0, shortpathW, -1, shortpath, shortlen, NULL, NULL);
1340 ret--; /* length without 0 */
1344 RtlFreeUnicodeString(&longpathW);
1349 /***********************************************************************
1350 * GetLongPathNameW (KERNEL32.@)
1353 * observed (Win2000):
1354 * shortpath=NULL: LastError=ERROR_INVALID_PARAMETER, ret=0
1355 * shortpath="": LastError=ERROR_PATH_NOT_FOUND, ret=0
1357 DWORD WINAPI GetLongPathNameW( LPCWSTR shortpath, LPWSTR longpath, DWORD longlen )
1359 DOS_FULL_NAME full_name;
1366 SetLastError(ERROR_INVALID_PARAMETER);
1369 if (!shortpath[0]) {
1370 SetLastError(ERROR_PATH_NOT_FOUND);
1374 TRACE("%s,%p,%ld\n", debugstr_w(shortpath), longpath, longlen);
1376 if(shortpath[0]=='\\' && shortpath[1]=='\\')
1378 ERR("UNC pathname %s\n",debugstr_w(shortpath));
1379 lstrcpynW( longpath, full_name.short_name, longlen );
1380 return strlenW(longpath);
1383 if (!DOSFS_GetFullName( shortpath, TRUE, &full_name )) return 0;
1385 root = full_name.long_name;
1386 drive = DRIVE_FindDriveRoot(&root);
1388 ret = MultiByteToWideChar(CP_UNIXCP, 0, root, -1, NULL, 0);
1390 /* reproduce terminating slash */
1391 if (ret > 4) /* if not drive root */
1393 len = strlenW(shortpath);
1394 if (shortpath[len - 1] == '\\' || shortpath[len - 1] == '/')
1400 longpath[0] = 'A' + drive;
1402 MultiByteToWideChar(CP_UNIXCP, 0, root, -1, longpath + 2, longlen - 2);
1403 for (p = longpath; *p; p++) if (*p == '/') *p = '\\';
1406 longpath[ret - 2] = '\\';
1407 longpath[ret - 1] = 0;
1409 TRACE("returning %s\n", debugstr_w(longpath));
1410 ret--; /* length without 0 */
1416 /***********************************************************************
1417 * GetLongPathNameA (KERNEL32.@)
1419 DWORD WINAPI GetLongPathNameA( LPCSTR shortpath, LPSTR longpath, DWORD longlen )
1421 UNICODE_STRING shortpathW;
1422 WCHAR longpathW[MAX_PATH];
1427 SetLastError(ERROR_INVALID_PARAMETER);
1431 TRACE("%s\n", debugstr_a(shortpath));
1433 if (!RtlCreateUnicodeStringFromAsciiz(&shortpathW, shortpath))
1435 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
1439 retW = GetLongPathNameW(shortpathW.Buffer, longpathW, MAX_PATH);
1443 else if (retW > MAX_PATH)
1445 SetLastError(ERROR_FILENAME_EXCED_RANGE);
1450 ret = WideCharToMultiByte(CP_ACP, 0, longpathW, -1, NULL, 0, NULL, NULL);
1453 WideCharToMultiByte(CP_ACP, 0, longpathW, -1, longpath, longlen, NULL, NULL);
1454 ret--; /* length without 0 */
1458 RtlFreeUnicodeString(&shortpathW);
1463 /***********************************************************************
1464 * DOSFS_DoGetFullPathName
1466 * Implementation of GetFullPathNameA/W.
1468 * bon@elektron 000331:
1469 * A test for GetFullPathName with many pathological cases
1470 * now gives identical output for Wine and OSR2
1472 static DWORD DOSFS_DoGetFullPathName( LPCWSTR name, DWORD len, LPWSTR result )
1475 DOS_FULL_NAME full_name;
1479 WCHAR drivecur[] = {'C',':','.',0};
1480 WCHAR driveletter=0;
1481 int namelen,drive=0;
1482 static const WCHAR bkslashW[] = {'\\',0};
1483 static const WCHAR dotW[] = {'.',0};
1484 static const WCHAR updir_slashW[] = {'\\','.','.','\\',0};
1485 static const WCHAR curdirW[] = {'\\','.','\\',0};
1486 static const WCHAR updirW[] = {'\\','.','.',0};
1490 SetLastError(ERROR_BAD_PATHNAME);
1494 TRACE("passed %s\n", debugstr_w(name));
1497 /*drive letter given */
1499 driveletter = name[0];
1501 if ((name[1]==':') && ((name[2]=='\\') || (name[2]=='/')))
1502 /*absolute path given */
1504 strncpyW(full_name.short_name, name, MAX_PATHNAME_LEN);
1505 full_name.short_name[MAX_PATHNAME_LEN - 1] = 0; /* ensure 0 termination */
1506 drive = toupperW(name[0]) - 'A';
1511 drivecur[0]=driveletter;
1512 else if ((name[0]=='\\') || (name[0]=='/'))
1513 strcpyW(drivecur, bkslashW);
1515 strcpyW(drivecur, dotW);
1517 if (!DOSFS_GetFullName( drivecur, FALSE, &full_name ))
1519 FIXME("internal: error getting drive/path\n");
1522 /* find path that drive letter substitutes*/
1523 drive = toupperW(full_name.short_name[0]) - 'A';
1524 root= DRIVE_GetRoot(drive);
1527 FIXME("internal: error getting DOS Drive Root\n");
1530 if (!strcmp(root,"/"))
1532 /* we have just the last / and we need it. */
1533 p_l = full_name.long_name;
1537 p_l = full_name.long_name + strlen(root);
1539 /* append long name (= unix name) to drive */
1540 MultiByteToWideChar(CP_UNIXCP, 0, p_l, -1, full_name.short_name + 2, MAX_PATHNAME_LEN - 3);
1541 /* append name to treat */
1542 namelen= strlenW(full_name.short_name);
1545 p += 2; /* skip drive name when appending */
1546 if (namelen + 2 + strlenW(p) > MAX_PATHNAME_LEN)
1548 FIXME("internal error: buffer too small\n");
1551 full_name.short_name[namelen++] ='\\';
1552 full_name.short_name[namelen] = 0;
1553 strncpyW(full_name.short_name + namelen, p, MAX_PATHNAME_LEN - namelen);
1554 full_name.short_name[MAX_PATHNAME_LEN - 1] = 0; /* ensure 0 termination */
1556 /* reverse all slashes */
1557 for (p=full_name.short_name;
1558 p < full_name.short_name + strlenW(full_name.short_name);
1564 /* Use memmove, as areas overlap */
1566 while ((p = strstrW(full_name.short_name, updir_slashW)))
1568 if (p > full_name.short_name+2)
1571 q = strrchrW(full_name.short_name, '\\');
1572 memmove(q+1, p+4, (strlenW(p+4)+1) * sizeof(WCHAR));
1576 memmove(full_name.short_name+3, p+4, (strlenW(p+4)+1) * sizeof(WCHAR));
1579 if ((full_name.short_name[2]=='.')&&(full_name.short_name[3]=='.'))
1581 /* This case istn't treated yet : c:..\test */
1582 memmove(full_name.short_name+2,full_name.short_name+4,
1583 (strlenW(full_name.short_name+4)+1) * sizeof(WCHAR));
1586 while ((p = strstrW(full_name.short_name, curdirW)))
1589 memmove(p+1, p+3, (strlenW(p+3)+1) * sizeof(WCHAR));
1591 if (!(DRIVE_GetFlags(drive) & DRIVE_CASE_PRESERVING))
1592 for (p = full_name.short_name; *p; p++) *p = toupperW(*p);
1593 namelen = strlenW(full_name.short_name);
1594 if (!strcmpW(full_name.short_name+namelen-3, updirW))
1596 /* one more strange case: "c:\test\test1\.."
1598 *(full_name.short_name+namelen-3)=0;
1599 q = strrchrW(full_name.short_name, '\\');
1602 if (full_name.short_name[namelen-1]=='.')
1603 full_name.short_name[(namelen--)-1] =0;
1605 if (full_name.short_name[namelen-1]=='\\')
1606 full_name.short_name[(namelen--)-1] =0;
1607 TRACE("got %s\n", debugstr_w(full_name.short_name));
1609 /* If the lpBuffer buffer is too small, the return value is the
1610 size of the buffer, in characters, required to hold the path
1611 plus the terminating \0 (tested against win95osr2, bon 001118)
1613 ret = strlenW(full_name.short_name);
1616 /* don't touch anything when the buffer is not large enough */
1617 SetLastError( ERROR_INSUFFICIENT_BUFFER );
1622 strncpyW( result, full_name.short_name, len );
1623 result[len - 1] = 0; /* ensure 0 termination */
1626 TRACE("returning %s\n", debugstr_w(full_name.short_name) );
1631 /***********************************************************************
1632 * GetFullPathNameA (KERNEL32.@)
1634 * if the path closed with '\', *lastpart is 0
1636 DWORD WINAPI GetFullPathNameA( LPCSTR name, DWORD len, LPSTR buffer,
1639 UNICODE_STRING nameW;
1640 WCHAR bufferW[MAX_PATH];
1645 SetLastError(ERROR_INVALID_PARAMETER);
1649 if (!RtlCreateUnicodeStringFromAsciiz(&nameW, name))
1651 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
1655 retW = GetFullPathNameW( nameW.Buffer, MAX_PATH, bufferW, NULL);
1659 else if (retW > MAX_PATH)
1661 SetLastError(ERROR_FILENAME_EXCED_RANGE);
1666 ret = WideCharToMultiByte(CP_ACP, 0, bufferW, -1, NULL, 0, NULL, NULL);
1669 WideCharToMultiByte(CP_ACP, 0, bufferW, -1, buffer, len, NULL, NULL);
1670 ret--; /* length without 0 */
1674 LPSTR p = buffer + strlen(buffer);
1678 while ((p > buffer + 2) && (*p != '\\')) p--;
1681 else *lastpart = NULL;
1686 RtlFreeUnicodeString(&nameW);
1691 /***********************************************************************
1692 * GetFullPathNameW (KERNEL32.@)
1694 DWORD WINAPI GetFullPathNameW( LPCWSTR name, DWORD len, LPWSTR buffer,
1697 DWORD ret = DOSFS_DoGetFullPathName( name, len, buffer );
1698 if (ret && (ret<=len) && buffer && lastpart)
1700 LPWSTR p = buffer + strlenW(buffer);
1701 if (*p != (WCHAR)'\\')
1703 while ((p > buffer + 2) && (*p != (WCHAR)'\\')) p--;
1706 else *lastpart = NULL;
1712 /***********************************************************************
1713 * wine_get_unix_file_name (KERNEL32.@) Not a Windows API
1715 * Return the full Unix file name for a given path.
1716 * FIXME: convert dos file name to unicode
1718 BOOL WINAPI wine_get_unix_file_name( LPCSTR dos, LPSTR buffer, DWORD len )
1722 WCHAR dosW[MAX_PATHNAME_LEN];
1724 MultiByteToWideChar(CP_ACP, 0, dos, -1, dosW, MAX_PATHNAME_LEN);
1725 ret = DOSFS_GetFullName( dosW, FALSE, &path );
1728 strncpy( buffer, path.long_name, len );
1729 buffer[len - 1] = 0; /* ensure 0 termination */
1735 /***********************************************************************
1736 * get_show_dir_symlinks_option
1738 static BOOL get_show_dir_symlinks_option(void)
1740 static const WCHAR WineW[] = {'M','a','c','h','i','n','e','\\',
1741 'S','o','f','t','w','a','r','e','\\',
1742 'W','i','n','e','\\','W','i','n','e','\\',
1743 'C','o','n','f','i','g','\\','W','i','n','e',0};
1744 static const WCHAR ShowDirSymlinksW[] = {'S','h','o','w','D','i','r','S','y','m','l','i','n','k','s',0};
1749 OBJECT_ATTRIBUTES attr;
1750 UNICODE_STRING nameW;
1753 attr.Length = sizeof(attr);
1754 attr.RootDirectory = 0;
1755 attr.ObjectName = &nameW;
1756 attr.Attributes = 0;
1757 attr.SecurityDescriptor = NULL;
1758 attr.SecurityQualityOfService = NULL;
1759 RtlInitUnicodeString( &nameW, WineW );
1761 if (!NtOpenKey( &hkey, KEY_ALL_ACCESS, &attr ))
1763 RtlInitUnicodeString( &nameW, ShowDirSymlinksW );
1764 if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
1766 WCHAR *str = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
1767 ret = IS_OPTION_TRUE( str[0] );
1775 /***********************************************************************
1778 static int DOSFS_FindNextEx( FIND_FIRST_INFO *info, WIN32_FIND_DATAW *entry )
1780 DWORD attr = info->attr | FA_UNUSED | FA_ARCHIVE | FA_RDONLY;
1781 UINT flags = DRIVE_GetFlags( info->drive );
1782 char *p, buffer[MAX_PATHNAME_LEN];
1783 const char *drive_path;
1785 LPCWSTR long_name, short_name;
1786 BY_HANDLE_FILE_INFORMATION fileinfo;
1790 if ((info->attr & ~(FA_UNUSED | FA_ARCHIVE | FA_RDONLY)) == FA_LABEL)
1792 if (info->cur_pos) return 0;
1793 entry->dwFileAttributes = FILE_ATTRIBUTE_LABEL;
1794 RtlSecondsSince1970ToTime( (time_t)0, (LARGE_INTEGER *)&entry->ftCreationTime );
1795 RtlSecondsSince1970ToTime( (time_t)0, (LARGE_INTEGER *)&entry->ftLastAccessTime );
1796 RtlSecondsSince1970ToTime( (time_t)0, (LARGE_INTEGER *)&entry->ftLastWriteTime );
1797 entry->nFileSizeHigh = 0;
1798 entry->nFileSizeLow = 0;
1799 entry->dwReserved0 = 0;
1800 entry->dwReserved1 = 0;
1801 DOSFS_ToDosDTAFormat( DRIVE_GetLabel( info->drive ), entry->cFileName );
1802 strcpyW( entry->cAlternateFileName, entry->cFileName );
1804 TRACE("returning %s (%s) as label\n",
1805 debugstr_w(entry->cFileName), debugstr_w(entry->cAlternateFileName));
1809 drive_path = info->path + strlen(DRIVE_GetRoot( info->drive ));
1810 while ((*drive_path == '/') || (*drive_path == '\\')) drive_path++;
1811 drive_root = !*drive_path;
1813 lstrcpynA( buffer, info->path, sizeof(buffer) - 1 );
1814 strcat( buffer, "/" );
1815 p = buffer + strlen(buffer);
1817 while (DOSFS_ReadDir( info->u.dos_dir, &long_name, &short_name ))
1821 /* Don't return '.' and '..' in the root of the drive */
1822 if (drive_root && (long_name[0] == '.') &&
1823 (!long_name[1] || ((long_name[1] == '.') && !long_name[2])))
1826 /* Check the long mask */
1828 if (info->long_mask && *info->long_mask)
1830 if (!DOSFS_MatchLong( info->long_mask, long_name,
1831 flags & DRIVE_CASE_SENSITIVE )) continue;
1834 /* Check the short mask */
1836 if (info->short_mask)
1840 DOSFS_Hash( long_name, dos_name, TRUE,
1841 !(flags & DRIVE_CASE_SENSITIVE) );
1842 short_name = dos_name;
1844 if (!DOSFS_MatchShort( info->short_mask, short_name )) continue;
1847 /* Check the file attributes */
1848 WideCharToMultiByte(CP_UNIXCP, 0, long_name, -1,
1849 p, sizeof(buffer) - (int)(p - buffer), NULL, NULL);
1850 if (!FILE_Stat( buffer, &fileinfo, &is_symlink ))
1852 WARN("can't stat %s\n", buffer);
1855 if (is_symlink && (fileinfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
1857 static int show_dir_symlinks = -1;
1858 if (show_dir_symlinks == -1)
1859 show_dir_symlinks = get_show_dir_symlinks_option();
1860 if (!show_dir_symlinks) continue;
1863 if (fileinfo.dwFileAttributes & ~attr) continue;
1865 /* We now have a matching entry; fill the result and return */
1867 entry->dwFileAttributes = fileinfo.dwFileAttributes;
1868 entry->ftCreationTime = fileinfo.ftCreationTime;
1869 entry->ftLastAccessTime = fileinfo.ftLastAccessTime;
1870 entry->ftLastWriteTime = fileinfo.ftLastWriteTime;
1871 entry->nFileSizeHigh = fileinfo.nFileSizeHigh;
1872 entry->nFileSizeLow = fileinfo.nFileSizeLow;
1875 DOSFS_ToDosDTAFormat( short_name, entry->cAlternateFileName );
1877 DOSFS_Hash( long_name, entry->cAlternateFileName, FALSE,
1878 !(flags & DRIVE_CASE_SENSITIVE) );
1880 lstrcpynW( entry->cFileName, long_name, sizeof(entry->cFileName)/sizeof(entry->cFileName[0]) );
1881 if (!(flags & DRIVE_CASE_PRESERVING)) strlwrW( entry->cFileName );
1882 TRACE("returning %s (%s) %02lx %ld\n",
1883 debugstr_w(entry->cFileName), debugstr_w(entry->cAlternateFileName),
1884 entry->dwFileAttributes, entry->nFileSizeLow );
1887 return 0; /* End of directory */
1890 /***********************************************************************
1893 * Find the next matching file. Return the number of entries read to find
1894 * the matching one, or 0 if no more entries.
1895 * 'short_mask' is the 8.3 mask (in FCB format), 'long_mask' is the long
1896 * file name mask. Either or both can be NULL.
1898 * NOTE: This is supposed to be only called by the int21 emulation
1899 * routines, and so assumes that the Win16Mutex is held to
1900 * protect the static directory cache.
1902 int DOSFS_FindNext( const char *path, const char *short_mask,
1903 const char *long_mask, int drive, BYTE attr,
1904 int skip, WIN32_FIND_DATAA *entry )
1906 static FIND_FIRST_INFO info;
1907 LPCWSTR short_name, long_name;
1909 UNICODE_STRING short_maskW, long_maskW;
1910 WIN32_FIND_DATAW entryW;
1912 TRACE("(%s, %s, %s, %x, %x, %x, %p)\n", debugstr_a(path),
1913 debugstr_a(short_mask), debugstr_a(long_mask), drive, attr, skip,
1916 RtlCreateUnicodeStringFromAsciiz(&short_maskW, short_mask);
1917 RtlCreateUnicodeStringFromAsciiz(&long_maskW, long_mask);
1919 /* Check the cached directory */
1920 if (!(info.u.dos_dir && info.path == path && !strcmpW(info.short_mask, short_maskW.Buffer)
1921 && !strcmpW(info.long_mask, long_maskW.Buffer) && info.drive == drive
1922 && info.attr == attr && info.cur_pos <= skip))
1924 /* Not in the cache, open it anew */
1925 if (info.u.dos_dir) DOSFS_CloseDir( info.u.dos_dir );
1927 info.path = (LPSTR)path;
1928 RtlFreeHeap(GetProcessHeap(), 0, info.long_mask);
1929 RtlFreeHeap(GetProcessHeap(), 0, info.short_mask);
1930 info.long_mask = long_maskW.Buffer;
1931 info.short_mask = short_maskW.Buffer;
1935 info.u.dos_dir = DOSFS_OpenDir( info.path );
1939 RtlFreeUnicodeString(&short_maskW);
1940 RtlFreeUnicodeString(&long_maskW);
1943 /* Skip to desired position */
1944 while (info.cur_pos < skip)
1945 if (info.u.dos_dir && DOSFS_ReadDir( info.u.dos_dir, &long_name, &short_name ))
1950 if (info.u.dos_dir && info.cur_pos == skip && DOSFS_FindNextEx( &info, &entryW ))
1952 WideCharToMultiByte(CP_ACP, 0, entryW.cFileName, -1,
1953 entry->cFileName, sizeof(entry->cFileName), NULL, NULL);
1954 WideCharToMultiByte(CP_ACP, 0, entryW.cAlternateFileName, -1,
1955 entry->cAlternateFileName, sizeof(entry->cAlternateFileName), NULL, NULL);
1956 count = info.cur_pos - skip;
1958 entry->dwFileAttributes = entryW.dwFileAttributes;
1959 entry->nFileSizeHigh = entryW.nFileSizeHigh;
1960 entry->nFileSizeLow = entryW.nFileSizeLow;
1961 entry->ftCreationTime = entryW.ftCreationTime;
1962 entry->ftLastAccessTime = entryW.ftLastAccessTime;
1963 entry->ftLastWriteTime = entryW.ftLastWriteTime;
1971 if (info.u.dos_dir) DOSFS_CloseDir( info.u.dos_dir );
1972 memset( &info, '\0', sizeof(info) );
1978 /*************************************************************************
1979 * FindFirstFileExW (KERNEL32.@)
1981 HANDLE WINAPI FindFirstFileExW(
1983 FINDEX_INFO_LEVELS fInfoLevelId,
1984 LPVOID lpFindFileData,
1985 FINDEX_SEARCH_OPS fSearchOp,
1986 LPVOID lpSearchFilter,
1987 DWORD dwAdditionalFlags)
1989 FIND_FIRST_INFO *info;
1993 SetLastError(ERROR_PATH_NOT_FOUND);
1994 return INVALID_HANDLE_VALUE;
1997 if ((fSearchOp != FindExSearchNameMatch) || (dwAdditionalFlags != 0))
1999 FIXME("options not implemented 0x%08x 0x%08lx\n", fSearchOp, dwAdditionalFlags );
2000 return INVALID_HANDLE_VALUE;
2003 switch(fInfoLevelId)
2005 case FindExInfoStandard:
2007 WIN32_FIND_DATAW * data = (WIN32_FIND_DATAW *) lpFindFileData;
2011 data->dwReserved0 = data->dwReserved1 = 0x0;
2012 if (lpFileName[0] == '\\' && lpFileName[1] == '\\')
2014 ERR("UNC path name\n");
2015 if (!(info = HeapAlloc( GetProcessHeap(), 0, sizeof(FIND_FIRST_INFO)))) break;
2016 info->u.smb_dir = SMB_FindFirst(lpFileName);
2017 if(!info->u.smb_dir)
2019 HeapFree(GetProcessHeap(), 0, info);
2023 RtlInitializeCriticalSection( &info->cs );
2027 DOS_FULL_NAME full_name;
2029 if (lpFileName[0] && lpFileName[1] == ':')
2031 /* don't allow root directories */
2032 if (!lpFileName[2] ||
2033 ((lpFileName[2] == '/' || lpFileName[2] == '\\') && !lpFileName[3]))
2035 SetLastError(ERROR_FILE_NOT_FOUND);
2036 return INVALID_HANDLE_VALUE;
2039 if (!DOSFS_GetFullName( lpFileName, FALSE, &full_name )) break;
2040 if (!(info = HeapAlloc( GetProcessHeap(), 0, sizeof(FIND_FIRST_INFO)))) break;
2041 RtlInitializeCriticalSection( &info->cs );
2042 info->path = HeapAlloc( GetProcessHeap(), 0, strlen(full_name.long_name)+1 );
2043 strcpy( info->path, full_name.long_name );
2045 p = strrchr( info->path, '/' );
2047 long_mask_len = MultiByteToWideChar(CP_UNIXCP, 0, p, -1, NULL, 0);
2048 info->long_mask = HeapAlloc( GetProcessHeap(), 0, long_mask_len * sizeof(WCHAR) );
2049 MultiByteToWideChar(CP_UNIXCP, 0, p, -1, info->long_mask, long_mask_len);
2051 info->short_mask = NULL;
2053 info->drive = full_name.drive;
2056 info->u.dos_dir = DOSFS_OpenDir( info->path );
2058 if (!FindNextFileW( (HANDLE) info, data ))
2060 FindClose( (HANDLE) info );
2061 SetLastError( ERROR_FILE_NOT_FOUND );
2064 return (HANDLE) info;
2068 FIXME("fInfoLevelId 0x%08x not implemented\n", fInfoLevelId );
2070 return INVALID_HANDLE_VALUE;
2073 /*************************************************************************
2074 * FindFirstFileA (KERNEL32.@)
2076 HANDLE WINAPI FindFirstFileA(
2078 WIN32_FIND_DATAA *lpFindData )
2080 return FindFirstFileExA(lpFileName, FindExInfoStandard, lpFindData,
2081 FindExSearchNameMatch, NULL, 0);
2084 /*************************************************************************
2085 * FindFirstFileExA (KERNEL32.@)
2087 HANDLE WINAPI FindFirstFileExA(
2089 FINDEX_INFO_LEVELS fInfoLevelId,
2090 LPVOID lpFindFileData,
2091 FINDEX_SEARCH_OPS fSearchOp,
2092 LPVOID lpSearchFilter,
2093 DWORD dwAdditionalFlags)
2096 WIN32_FIND_DATAA *dataA;
2097 WIN32_FIND_DATAW dataW;
2098 UNICODE_STRING pathW;
2102 SetLastError(ERROR_PATH_NOT_FOUND);
2103 return INVALID_HANDLE_VALUE;
2106 if (!RtlCreateUnicodeStringFromAsciiz(&pathW, lpFileName))
2108 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
2109 return INVALID_HANDLE_VALUE;
2112 handle = FindFirstFileExW(pathW.Buffer, fInfoLevelId, &dataW, fSearchOp, lpSearchFilter, dwAdditionalFlags);
2113 RtlFreeUnicodeString(&pathW);
2114 if (handle == INVALID_HANDLE_VALUE) return handle;
2116 dataA = (WIN32_FIND_DATAA *) lpFindFileData;
2117 dataA->dwFileAttributes = dataW.dwFileAttributes;
2118 dataA->ftCreationTime = dataW.ftCreationTime;
2119 dataA->ftLastAccessTime = dataW.ftLastAccessTime;
2120 dataA->ftLastWriteTime = dataW.ftLastWriteTime;
2121 dataA->nFileSizeHigh = dataW.nFileSizeHigh;
2122 dataA->nFileSizeLow = dataW.nFileSizeLow;
2123 WideCharToMultiByte( CP_ACP, 0, dataW.cFileName, -1,
2124 dataA->cFileName, sizeof(dataA->cFileName), NULL, NULL );
2125 WideCharToMultiByte( CP_ACP, 0, dataW.cAlternateFileName, -1,
2126 dataA->cAlternateFileName, sizeof(dataA->cAlternateFileName), NULL, NULL );
2130 /*************************************************************************
2131 * FindFirstFileW (KERNEL32.@)
2133 HANDLE WINAPI FindFirstFileW( LPCWSTR lpFileName, WIN32_FIND_DATAW *lpFindData )
2135 return FindFirstFileExW(lpFileName, FindExInfoStandard, lpFindData,
2136 FindExSearchNameMatch, NULL, 0);
2139 /*************************************************************************
2140 * FindNextFileW (KERNEL32.@)
2142 BOOL WINAPI FindNextFileW( HANDLE handle, WIN32_FIND_DATAW *data )
2144 FIND_FIRST_INFO *info;
2146 DWORD gle = ERROR_NO_MORE_FILES;
2148 if (handle == INVALID_HANDLE_VALUE)
2150 SetLastError( ERROR_INVALID_HANDLE );
2153 info = (FIND_FIRST_INFO*) handle;
2154 RtlEnterCriticalSection( &info->cs );
2155 if (info->drive == -1)
2157 ret = SMB_FindNext( info->u.smb_dir, data );
2160 SMB_CloseDir( info->u.smb_dir );
2161 HeapFree( GetProcessHeap(), 0, info->path );
2165 else if (!info->path || !info->u.dos_dir)
2169 else if (!DOSFS_FindNextEx( info, data ))
2171 DOSFS_CloseDir( info->u.dos_dir ); info->u.dos_dir = NULL;
2172 HeapFree( GetProcessHeap(), 0, info->path );
2174 HeapFree( GetProcessHeap(), 0, info->long_mask );
2175 info->long_mask = NULL;
2180 RtlLeaveCriticalSection( &info->cs );
2181 if( !ret ) SetLastError( gle );
2186 /*************************************************************************
2187 * FindNextFileA (KERNEL32.@)
2189 BOOL WINAPI FindNextFileA( HANDLE handle, WIN32_FIND_DATAA *data )
2191 WIN32_FIND_DATAW dataW;
2192 if (!FindNextFileW( handle, &dataW )) return FALSE;
2193 data->dwFileAttributes = dataW.dwFileAttributes;
2194 data->ftCreationTime = dataW.ftCreationTime;
2195 data->ftLastAccessTime = dataW.ftLastAccessTime;
2196 data->ftLastWriteTime = dataW.ftLastWriteTime;
2197 data->nFileSizeHigh = dataW.nFileSizeHigh;
2198 data->nFileSizeLow = dataW.nFileSizeLow;
2199 WideCharToMultiByte( CP_ACP, 0, dataW.cFileName, -1,
2200 data->cFileName, sizeof(data->cFileName), NULL, NULL );
2201 WideCharToMultiByte( CP_ACP, 0, dataW.cAlternateFileName, -1,
2202 data->cAlternateFileName,
2203 sizeof(data->cAlternateFileName), NULL, NULL );
2207 /*************************************************************************
2208 * FindClose (KERNEL32.@)
2210 BOOL WINAPI FindClose( HANDLE handle )
2212 FIND_FIRST_INFO *info = (FIND_FIRST_INFO*) handle;
2214 if (handle == INVALID_HANDLE_VALUE) goto error;
2218 RtlEnterCriticalSection( &info->cs );
2221 if (info->u.dos_dir) DOSFS_CloseDir( info->u.dos_dir );
2222 if (info->path) HeapFree( GetProcessHeap(), 0, info->path );
2223 if (info->long_mask) HeapFree( GetProcessHeap(), 0, info->long_mask );
2226 __EXCEPT(page_fault)
2228 WARN("Illegal handle %p\n", handle);
2229 SetLastError( ERROR_INVALID_HANDLE );
2233 if (!info) goto error;
2234 RtlLeaveCriticalSection( &info->cs );
2235 RtlDeleteCriticalSection( &info->cs );
2236 HeapFree(GetProcessHeap(), 0, info);
2240 SetLastError( ERROR_INVALID_HANDLE );
2244 /***********************************************************************
2245 * MulDiv (KERNEL32.@)
2247 * Result of multiplication and division
2248 * -1: Overflow occurred or Divisor was 0
2255 #if SIZEOF_LONG_LONG >= 8
2258 if (!nDivisor) return -1;
2260 /* We want to deal with a positive divisor to simplify the logic. */
2263 nMultiplicand = - nMultiplicand;
2264 nDivisor = -nDivisor;
2267 /* If the result is positive, we "add" to round. else, we subtract to round. */
2268 if ( ( (nMultiplicand < 0) && (nMultiplier < 0) ) ||
2269 ( (nMultiplicand >= 0) && (nMultiplier >= 0) ) )
2270 ret = (((long long)nMultiplicand * nMultiplier) + (nDivisor/2)) / nDivisor;
2272 ret = (((long long)nMultiplicand * nMultiplier) - (nDivisor/2)) / nDivisor;
2274 if ((ret > 2147483647) || (ret < -2147483647)) return -1;
2277 if (!nDivisor) return -1;
2279 /* We want to deal with a positive divisor to simplify the logic. */
2282 nMultiplicand = - nMultiplicand;
2283 nDivisor = -nDivisor;
2286 /* If the result is positive, we "add" to round. else, we subtract to round. */
2287 if ( ( (nMultiplicand < 0) && (nMultiplier < 0) ) ||
2288 ( (nMultiplicand >= 0) && (nMultiplier >= 0) ) )
2289 return ((nMultiplicand * nMultiplier) + (nDivisor/2)) / nDivisor;
2291 return ((nMultiplicand * nMultiplier) - (nDivisor/2)) / nDivisor;
2297 /***********************************************************************
2298 * DosDateTimeToFileTime (KERNEL32.@)
2300 BOOL WINAPI DosDateTimeToFileTime( WORD fatdate, WORD fattime, LPFILETIME ft)
2305 time_t time1, time2;
2308 newtm.tm_sec = (fattime & 0x1f) * 2;
2309 newtm.tm_min = (fattime >> 5) & 0x3f;
2310 newtm.tm_hour = (fattime >> 11);
2311 newtm.tm_mday = (fatdate & 0x1f);
2312 newtm.tm_mon = ((fatdate >> 5) & 0x0f) - 1;
2313 newtm.tm_year = (fatdate >> 9) + 80;
2315 RtlSecondsSince1970ToTime( timegm(&newtm), (LARGE_INTEGER *)ft );
2317 time1 = mktime(&newtm);
2318 gtm = gmtime(&time1);
2319 time2 = mktime(gtm);
2320 RtlSecondsSince1970ToTime( 2*time1-time2, (LARGE_INTEGER *)ft );
2326 /***********************************************************************
2327 * FileTimeToDosDateTime (KERNEL32.@)
2329 BOOL WINAPI FileTimeToDosDateTime( const FILETIME *ft, LPWORD fatdate,
2337 li.s.LowPart = ft->dwLowDateTime;
2338 li.s.HighPart = ft->dwHighDateTime;
2339 RtlTimeToSecondsSince1970( &li, &t );
2341 tm = gmtime( &unixtime );
2343 *fattime = (tm->tm_hour << 11) + (tm->tm_min << 5) + (tm->tm_sec / 2);
2345 *fatdate = ((tm->tm_year - 80) << 9) + ((tm->tm_mon + 1) << 5)
2351 /***********************************************************************
2352 * QueryDosDeviceA (KERNEL32.@)
2354 * returns array of strings terminated by \0, terminated by \0
2356 DWORD WINAPI QueryDosDeviceA(LPCSTR devname,LPSTR target,DWORD bufsize)
2358 DWORD ret = 0, retW;
2359 LPWSTR targetW = (LPWSTR)HeapAlloc(GetProcessHeap(),0,
2360 bufsize * sizeof(WCHAR));
2361 UNICODE_STRING devnameW;
2363 if(devname) RtlCreateUnicodeStringFromAsciiz(&devnameW, devname);
2364 else devnameW.Buffer = NULL;
2366 retW = QueryDosDeviceW(devnameW.Buffer, targetW, bufsize);
2368 ret = WideCharToMultiByte(CP_ACP, 0, targetW, retW, target,
2369 bufsize, NULL, NULL);
2371 RtlFreeUnicodeString(&devnameW);
2372 if (targetW) HeapFree(GetProcessHeap(),0,targetW);
2377 /***********************************************************************
2378 * QueryDosDeviceW (KERNEL32.@)
2380 * returns array of strings terminated by \0, terminated by \0
2383 * - Win9x returns for all calls ERROR_INVALID_PARAMETER
2384 * - the returned devices for devname == NULL is far from complete
2385 * - its not checked that the returned device exist
2387 DWORD WINAPI QueryDosDeviceW(LPCWSTR devname,LPWSTR target,DWORD bufsize)
2389 const WCHAR *pDev, *pName, *pNum = NULL;
2393 TRACE("(%s,...)\n", debugstr_w(devname));
2395 /* return known MSDOS devices */
2398 static const WCHAR devices[][5] = {{'A','U','X',0},
2399 {'C','O','M','1',0},
2400 {'C','O','M','2',0},
2401 {'L','P','T','1',0},
2403 for(i=0; (i< (sizeof(devices)/sizeof(devices[0]))); i++) {
2404 DWORD len = strlenW(devices[i]);
2405 if(target && (bufsize >= ret + len + 2)) {
2406 strcpyW(target+ret, devices[i]);
2409 /* in this case WinXP returns 0 */
2410 FIXME("function return is wrong for WinXP!\n");
2411 SetLastError(ERROR_INSUFFICIENT_BUFFER);
2415 /* append drives here */
2416 if(target && bufsize > 0) target[ret++] = 0;
2417 FIXME("Returned list is not complete\n");
2420 /* In theory all that are possible and have been defined.
2421 * Now just those below, since mirc uses it to check for special files.
2423 * (It is more complex, and supports netmounted stuff, and \\.\ stuff,
2424 * but currently we just ignore that.)
2426 if (!strcmpiW(devname, auxW)) {
2431 } else if (!strcmpiW(devname, nulW)) {
2434 } else if (!strncmpiW(devname, comW, strlenW(comW))) {
2437 pNum = devname + strlenW(comW);
2438 for(numsiz=0; isdigitW(*(pNum+numsiz)); numsiz++);
2439 if(*(pNum + numsiz)) {
2440 SetLastError(ERROR_FILE_NOT_FOUND);
2443 } else if (!strncmpiW(devname, lptW, strlenW(lptW))) {
2446 pNum = devname + strlenW(lptW);
2447 for(numsiz=0; isdigitW(*(pNum+numsiz)); numsiz++);
2448 if(*(pNum + numsiz)) {
2449 SetLastError(ERROR_FILE_NOT_FOUND);
2453 /* This might be a DOS device we do not handle yet ... */
2454 FIXME("(%s) not detected as DOS device!\n",debugstr_w(devname));
2456 /* Win9x set the error ERROR_INVALID_PARAMETER */
2457 SetLastError(ERROR_FILE_NOT_FOUND);
2460 FIXME("device %s may not exist on this computer\n", debugstr_w(devname));
2462 ret = strlenW(pDev) + strlenW(pName) + numsiz + 2;
2463 if (ret > bufsize) ret = 0;
2464 if (target && ret) {
2465 strcpyW(target,pDev);
2466 strcatW(target,pName);
2467 if (pNum) strcatW(target,pNum);
2474 /***********************************************************************
2475 * DefineDosDeviceA (KERNEL32.@)
2477 BOOL WINAPI DefineDosDeviceA(DWORD flags,LPCSTR devname,LPCSTR targetpath) {
2478 FIXME("(0x%08lx,%s,%s),stub!\n",flags,devname,targetpath);
2479 SetLastError(ERROR_CALL_NOT_IMPLEMENTED);