Commit | Line | Data |
---|---|---|
4f8c37b4 AJ |
1 | /* |
2 | * DOS file system functions | |
3 | * | |
4 | * Copyright 1993 Erik Bos | |
5 | * Copyright 1996 Alexandre Julliard | |
0799c1a7 AJ |
6 | * |
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. | |
11 | * | |
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. | |
16 | * | |
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 | |
4f8c37b4 AJ |
20 | */ |
21 | ||
c7c217b3 | 22 | #include "config.h" |
402b79a1 | 23 | #include "wine/port.h" |
33929be4 | 24 | |
0c126c7c | 25 | #include <sys/types.h> |
4f8c37b4 AJ |
26 | #include <ctype.h> |
27 | #include <dirent.h> | |
44ed71f5 | 28 | #include <errno.h> |
13277480 | 29 | #ifdef HAVE_SYS_ERRNO_H |
d30dfd24 | 30 | #include <sys/errno.h> |
13277480 | 31 | #endif |
9ea19e54 | 32 | #include <fcntl.h> |
e37c6e18 | 33 | #include <stdarg.h> |
4f8c37b4 AJ |
34 | #include <string.h> |
35 | #include <stdlib.h> | |
4f8c37b4 | 36 | #include <sys/stat.h> |
9a624916 | 37 | #ifdef HAVE_SYS_IOCTL_H |
9ea19e54 | 38 | #include <sys/ioctl.h> |
48ac89b6 | 39 | #endif |
4f8c37b4 | 40 | #include <time.h> |
d016f819 PS |
41 | #ifdef HAVE_UNISTD_H |
42 | # include <unistd.h> | |
43 | #endif | |
4f8c37b4 | 44 | |
f3d2a8d4 EP |
45 | #define NONAMELESSUNION |
46 | #define NONAMELESSSTRUCT | |
e37c6e18 | 47 | #include "ntstatus.h" |
317af320 | 48 | #include "windef.h" |
e37c6e18 | 49 | #include "winbase.h" |
9ea19e54 | 50 | #include "winerror.h" |
33929be4 PS |
51 | #include "wingdi.h" |
52 | ||
53 | #include "wine/unicode.h" | |
54 | #include "wine/winbase16.h" | |
4f8c37b4 AJ |
55 | #include "drive.h" |
56 | #include "file.h" | |
9c1de6de | 57 | #include "winternl.h" |
37e9503a | 58 | #include "wine/server.h" |
402b79a1 | 59 | #include "wine/exception.h" |
737d4be8 | 60 | #include "excpt.h" |
33929be4 | 61 | |
963985b3 MM |
62 | #include "smb.h" |
63 | ||
0799c1a7 | 64 | #include "wine/debug.h" |
9ea19e54 | 65 | |
0799c1a7 AJ |
66 | WINE_DEFAULT_DEBUG_CHANNEL(dosfs); |
67 | WINE_DECLARE_DEBUG_CHANNEL(file); | |
b4b9fae6 | 68 | |
9ea19e54 | 69 | /* Define the VFAT ioctl to get both short and long file names */ |
c6c09442 | 70 | /* FIXME: is it possible to get this to work on other systems? */ |
9ea19e54 | 71 | #ifdef linux |
df2673b7 AJ |
72 | /* We want the real kernel dirent structure, not the libc one */ |
73 | typedef struct | |
74 | { | |
75 | long d_ino; | |
76 | long d_off; | |
77 | unsigned short d_reclen; | |
78 | char d_name[256]; | |
79 | } KERNEL_DIRENT; | |
80 | ||
0bac5e93 PG |
81 | #define VFAT_IOCTL_READDIR_BOTH _IOR('r', 1, KERNEL_DIRENT [2] ) |
82 | ||
9786b058 MJM |
83 | /* To avoid blocking on non-directories in DOSFS_OpenDir_VFAT*/ |
84 | #ifndef O_DIRECTORY | |
85 | # define O_DIRECTORY 0200000 /* must be directory */ | |
86 | #endif | |
87 | ||
9ea19e54 AJ |
88 | #else /* linux */ |
89 | #undef VFAT_IOCTL_READDIR_BOTH /* just in case... */ | |
90 | #endif /* linux */ | |
4f8c37b4 | 91 | |
e0deb0c6 AJ |
92 | #define IS_OPTION_TRUE(ch) ((ch) == 'y' || (ch) == 'Y' || (ch) == 't' || (ch) == 'T' || (ch) == '1') |
93 | ||
4f8c37b4 | 94 | /* Chars we don't want to see in DOS file names */ |
7e56f684 | 95 | #define INVALID_DOS_CHARS "*?<>|\"+=,;[] \345" |
4f8c37b4 | 96 | |
829fe323 AJ |
97 | static const DOS_DEVICE DOSFS_Devices[] = |
98 | /* name, device flags (see Int 21/AX=0x4400) */ | |
99 | { | |
d75aed2c DT |
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 } | |
4f8c37b4 AJ |
115 | }; |
116 | ||
a98f1297 SL |
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}; | |
119 | ||
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}; | |
124 | ||
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}; | |
129 | ||
d75aed2c DT |
130 | /* |
131 | * Directory info for DOSFS_ReadDir | |
132 | * contains the names of *all* the files in the directory | |
133 | */ | |
9ea19e54 AJ |
134 | typedef struct |
135 | { | |
d75aed2c DT |
136 | int used; |
137 | int size; | |
11d09a83 | 138 | WCHAR names[1]; |
9ea19e54 AJ |
139 | } DOS_DIR; |
140 | ||
85ed45e3 AJ |
141 | /* Info structure for FindFirstFile handle */ |
142 | typedef struct | |
143 | { | |
d75aed2c DT |
144 | char *path; /* unix path */ |
145 | LPWSTR long_mask; | |
85ed45e3 AJ |
146 | int drive; |
147 | int cur_pos; | |
33854afb | 148 | CRITICAL_SECTION cs; |
963985b3 MM |
149 | union |
150 | { | |
151 | DOS_DIR *dos_dir; | |
152 | SMB_DIR *smb_dir; | |
153 | } u; | |
85ed45e3 AJ |
154 | } FIND_FIRST_INFO; |
155 | ||
156 | ||
4f46b5de DS |
157 | static WINE_EXCEPTION_FILTER(page_fault) |
158 | { | |
159 | if (GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION) | |
160 | return EXCEPTION_EXECUTE_HANDLER; | |
161 | return EXCEPTION_CONTINUE_SEARCH; | |
162 | } | |
163 | ||
4f8c37b4 AJ |
164 | |
165 | /*********************************************************************** | |
166 | * DOSFS_ValidDOSName | |
167 | * | |
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 '/'. | |
171 | */ | |
d75aed2c | 172 | static int DOSFS_ValidDOSName( LPCWSTR name, int ignore_case ) |
4f8c37b4 | 173 | { |
1e37a181 | 174 | static const char invalid_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" INVALID_DOS_CHARS; |
d75aed2c | 175 | const WCHAR *p = name; |
1e37a181 | 176 | const char *invalid = ignore_case ? (invalid_chars + 26) : invalid_chars; |
4f8c37b4 AJ |
177 | int len = 0; |
178 | ||
179 | if (*p == '.') | |
180 | { | |
181 | /* Check for "." and ".." */ | |
182 | p++; | |
183 | if (*p == '.') p++; | |
184 | /* All other names beginning with '.' are invalid */ | |
185 | return (IS_END_OF_NAME(*p)); | |
186 | } | |
187 | while (!IS_END_OF_NAME(*p)) | |
188 | { | |
d75aed2c | 189 | if (*p < 256 && strchr( invalid, (char)*p )) return 0; /* Invalid char */ |
4f8c37b4 AJ |
190 | if (*p == '.') break; /* Start of the extension */ |
191 | if (++len > 8) return 0; /* Name too long */ | |
192 | p++; | |
193 | } | |
194 | if (*p != '.') return 1; /* End of name */ | |
195 | p++; | |
196 | if (IS_END_OF_NAME(*p)) return 0; /* Empty extension not allowed */ | |
197 | len = 0; | |
198 | while (!IS_END_OF_NAME(*p)) | |
199 | { | |
d75aed2c | 200 | if (*p < 256 && strchr( invalid, (char)*p )) return 0; /* Invalid char */ |
4f8c37b4 AJ |
201 | if (*p == '.') return 0; /* Second extension not allowed */ |
202 | if (++len > 3) return 0; /* Extension too long */ | |
203 | p++; | |
204 | } | |
205 | return 1; | |
206 | } | |
207 | ||
208 | ||
4f8c37b4 AJ |
209 | /*********************************************************************** |
210 | * DOSFS_ToDosFCBFormat | |
211 | * | |
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 '/'. | |
c6c09442 AJ |
215 | * Return FALSE if the name is not a valid DOS name. |
216 | * 'buffer' must be at least 12 characters long. | |
4f8c37b4 | 217 | */ |
d75aed2c | 218 | BOOL DOSFS_ToDosFCBFormat( LPCWSTR name, LPWSTR buffer ) |
4f8c37b4 AJ |
219 | { |
220 | static const char invalid_chars[] = INVALID_DOS_CHARS; | |
d75aed2c | 221 | LPCWSTR p = name; |
4f8c37b4 AJ |
222 | int i; |
223 | ||
224 | /* Check for "." and ".." */ | |
225 | if (*p == '.') | |
226 | { | |
227 | p++; | |
d75aed2c DT |
228 | buffer[0] = '.'; |
229 | for(i = 1; i < 11; i++) buffer[i] = ' '; | |
230 | buffer[11] = 0; | |
9ea19e54 AJ |
231 | if (*p == '.') |
232 | { | |
233 | buffer[1] = '.'; | |
234 | p++; | |
235 | } | |
c6c09442 | 236 | return (!*p || (*p == '/') || (*p == '\\')); |
4f8c37b4 AJ |
237 | } |
238 | ||
239 | for (i = 0; i < 8; i++) | |
240 | { | |
241 | switch(*p) | |
242 | { | |
243 | case '\0': | |
244 | case '\\': | |
245 | case '/': | |
246 | case '.': | |
247 | buffer[i] = ' '; | |
248 | break; | |
249 | case '?': | |
250 | p++; | |
251 | /* fall through */ | |
252 | case '*': | |
253 | buffer[i] = '?'; | |
254 | break; | |
255 | default: | |
d75aed2c DT |
256 | if (*p < 256 && strchr( invalid_chars, (char)*p )) return FALSE; |
257 | buffer[i] = toupperW(*p); | |
4f8c37b4 AJ |
258 | p++; |
259 | break; | |
260 | } | |
261 | } | |
262 | ||
263 | if (*p == '*') | |
264 | { | |
265 | /* Skip all chars after wildcard up to first dot */ | |
266 | while (*p && (*p != '/') && (*p != '\\') && (*p != '.')) p++; | |
267 | } | |
268 | else | |
269 | { | |
270 | /* Check if name too long */ | |
c6c09442 | 271 | if (*p && (*p != '/') && (*p != '\\') && (*p != '.')) return FALSE; |
4f8c37b4 AJ |
272 | } |
273 | if (*p == '.') p++; /* Skip dot */ | |
274 | ||
275 | for (i = 8; i < 11; i++) | |
276 | { | |
277 | switch(*p) | |
278 | { | |
279 | case '\0': | |
280 | case '\\': | |
281 | case '/': | |
282 | buffer[i] = ' '; | |
283 | break; | |
284 | case '.': | |
c6c09442 | 285 | return FALSE; /* Second extension not allowed */ |
4f8c37b4 AJ |
286 | case '?': |
287 | p++; | |
288 | /* fall through */ | |
289 | case '*': | |
290 | buffer[i] = '?'; | |
291 | break; | |
292 | default: | |
d75aed2c DT |
293 | if (*p < 256 && strchr( invalid_chars, (char)*p )) return FALSE; |
294 | buffer[i] = toupperW(*p); | |
4f8c37b4 AJ |
295 | p++; |
296 | break; | |
297 | } | |
298 | } | |
299 | buffer[11] = '\0'; | |
7cc51fae SL |
300 | |
301 | /* at most 3 character of the extension are processed | |
9a624916 | 302 | * is something behind this ? |
7cc51fae | 303 | */ |
199aebaa | 304 | while (*p == '*' || *p == ' ') p++; /* skip wildcards and spaces */ |
7cc51fae | 305 | return IS_END_OF_NAME(*p); |
4f8c37b4 AJ |
306 | } |
307 | ||
308 | ||
309 | /*********************************************************************** | |
310 | * DOSFS_ToDosDTAFormat | |
311 | * | |
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 '/'. | |
c6c09442 | 315 | * 'buffer' must be at least 13 characters long. |
4f8c37b4 | 316 | */ |
d75aed2c | 317 | static void DOSFS_ToDosDTAFormat( LPCWSTR name, LPWSTR buffer ) |
4f8c37b4 | 318 | { |
d75aed2c | 319 | LPWSTR p; |
4f8c37b4 | 320 | |
d75aed2c | 321 | memcpy( buffer, name, 8 * sizeof(WCHAR) ); |
566a52ad AJ |
322 | p = buffer + 8; |
323 | while ((p > buffer) && (p[-1] == ' ')) p--; | |
4f8c37b4 | 324 | *p++ = '.'; |
d75aed2c | 325 | memcpy( p, name + 8, 3 * sizeof(WCHAR) ); |
566a52ad AJ |
326 | p += 3; |
327 | while (p[-1] == ' ') p--; | |
4f8c37b4 AJ |
328 | if (p[-1] == '.') p--; |
329 | *p = '\0'; | |
4f8c37b4 AJ |
330 | } |
331 | ||
332 | ||
139a4b18 AJ |
333 | /*********************************************************************** |
334 | * DOSFS_MatchLong | |
335 | * | |
336 | * Check a long file name against a mask. | |
07291056 AM |
337 | * |
338 | * Tests (done in W95 DOS shell - case insensitive): | |
339 | * *.txt test1.test.txt * | |
340 | * *st1* test1.txt * | |
341 | * *.t??????.t* test1.ta.tornado.txt * | |
342 | * *tornado* test1.ta.tornado.txt * | |
343 | * t*t test1.ta.tornado.txt * | |
344 | * ?est* test1.txt * | |
345 | * ?est??? test1.txt - | |
9a624916 | 346 | * *test1.txt* test1.txt * |
07291056 | 347 | * h?l?o*t.dat hellothisisatest.dat * |
139a4b18 | 348 | */ |
d75aed2c | 349 | static int DOSFS_MatchLong( LPCWSTR mask, LPCWSTR name, int case_sensitive ) |
139a4b18 | 350 | { |
d75aed2c DT |
351 | LPCWSTR lastjoker = NULL; |
352 | LPCWSTR next_to_retry = NULL; | |
353 | static const WCHAR asterisk_dot_asterisk[] = {'*','.','*',0}; | |
07291056 | 354 | |
4610c0a9 GNJ |
355 | TRACE("(%s, %s, %x)\n", debugstr_w(mask), debugstr_w(name), case_sensitive); |
356 | ||
d75aed2c | 357 | if (!strcmpW( mask, asterisk_dot_asterisk )) return 1; |
139a4b18 AJ |
358 | while (*name && *mask) |
359 | { | |
360 | if (*mask == '*') | |
361 | { | |
362 | mask++; | |
363 | while (*mask == '*') mask++; /* Skip consecutive '*' */ | |
07291056 AM |
364 | lastjoker = mask; |
365 | if (!*mask) return 1; /* end of mask is all '*', so match */ | |
366 | ||
367 | /* skip to the next match after the joker(s) */ | |
139a4b18 | 368 | if (case_sensitive) while (*name && (*name != *mask)) name++; |
d75aed2c | 369 | else while (*name && (toupperW(*name) != toupperW(*mask))) name++; |
07291056 | 370 | |
9132a78b | 371 | if (!*name) break; |
07291056 | 372 | next_to_retry = name; |
139a4b18 AJ |
373 | } |
374 | else if (*mask != '?') | |
375 | { | |
07291056 | 376 | int mismatch = 0; |
139a4b18 AJ |
377 | if (case_sensitive) |
378 | { | |
07291056 AM |
379 | if (*mask != *name) mismatch = 1; |
380 | } | |
381 | else | |
382 | { | |
d75aed2c | 383 | if (toupperW(*mask) != toupperW(*name)) mismatch = 1; |
07291056 AM |
384 | } |
385 | if (!mismatch) | |
386 | { | |
387 | mask++; | |
388 | name++; | |
389 | if (*mask == '\0') | |
390 | { | |
391 | if (*name == '\0') | |
392 | return 1; | |
393 | if (lastjoker) | |
394 | mask = lastjoker; | |
395 | } | |
396 | } | |
397 | else /* mismatch ! */ | |
398 | { | |
399 | if (lastjoker) /* we had an '*', so we can try unlimitedly */ | |
400 | { | |
401 | mask = lastjoker; | |
402 | ||
403 | /* this scan sequence was a mismatch, so restart | |
404 | * 1 char after the first char we checked last time */ | |
405 | next_to_retry++; | |
406 | name = next_to_retry; | |
407 | } | |
408 | else | |
409 | return 0; /* bad luck */ | |
139a4b18 | 410 | } |
139a4b18 | 411 | } |
07291056 AM |
412 | else /* '?' */ |
413 | { | |
414 | mask++; | |
415 | name++; | |
416 | } | |
139a4b18 | 417 | } |
07291056 AM |
418 | while ((*mask == '.') || (*mask == '*')) |
419 | mask++; /* Ignore trailing '.' or '*' in mask */ | |
139a4b18 AJ |
420 | return (!*name && !*mask); |
421 | } | |
422 | ||
423 | ||
9ea19e54 | 424 | /*********************************************************************** |
d75aed2c DT |
425 | * DOSFS_AddDirEntry |
426 | * | |
427 | * Used to construct an array of filenames in DOSFS_OpenDir | |
9ea19e54 | 428 | */ |
d75aed2c | 429 | static BOOL DOSFS_AddDirEntry(DOS_DIR **dir, LPCWSTR name, LPCWSTR dosname) |
9ea19e54 | 430 | { |
11d09a83 MM |
431 | int extra1 = strlenW(name) + 1; |
432 | int extra2 = strlenW(dosname) + 1; | |
d75aed2c DT |
433 | |
434 | /* if we need more, at minimum double the size */ | |
435 | if( (extra1 + extra2 + (*dir)->used) > (*dir)->size) | |
9ea19e54 | 436 | { |
d75aed2c DT |
437 | int more = (*dir)->size; |
438 | DOS_DIR *t; | |
439 | ||
440 | if(more<(extra1+extra2)) | |
441 | more = extra1+extra2; | |
442 | ||
11d09a83 MM |
443 | t = HeapReAlloc(GetProcessHeap(), 0, *dir, sizeof(**dir) + |
444 | ((*dir)->size + more)*sizeof(WCHAR) ); | |
d75aed2c DT |
445 | if(!t) |
446 | { | |
447 | SetLastError( ERROR_NOT_ENOUGH_MEMORY ); | |
448 | ERR("Out of memory caching directory structure %d %d %d\n", | |
449 | (*dir)->size, more, (*dir)->used); | |
450 | return FALSE; | |
451 | } | |
452 | (*dir) = t; | |
453 | (*dir)->size += more; | |
9ea19e54 AJ |
454 | } |
455 | ||
d75aed2c | 456 | /* at this point, the dir structure is big enough to hold these names */ |
11d09a83 | 457 | strcpyW(&(*dir)->names[(*dir)->used], name); |
d75aed2c | 458 | (*dir)->used += extra1; |
11d09a83 | 459 | strcpyW(&(*dir)->names[(*dir)->used], dosname); |
d75aed2c DT |
460 | (*dir)->used += extra2; |
461 | ||
462 | return TRUE; | |
463 | } | |
a0d77315 | 464 | |
d75aed2c DT |
465 | |
466 | /*********************************************************************** | |
467 | * DOSFS_OpenDir_VFAT | |
468 | */ | |
49a0224f | 469 | static BOOL DOSFS_OpenDir_VFAT(DOS_DIR **dir, const char *unix_path) |
d75aed2c | 470 | { |
9ea19e54 | 471 | #ifdef VFAT_IOCTL_READDIR_BOTH |
d75aed2c | 472 | KERNEL_DIRENT de[2]; |
9786b058 | 473 | int fd = open( unix_path, O_RDONLY|O_DIRECTORY ); |
d75aed2c | 474 | BOOL r = TRUE; |
9ea19e54 AJ |
475 | |
476 | /* Check if the VFAT ioctl is supported on this directory */ | |
477 | ||
d75aed2c DT |
478 | if ( fd<0 ) |
479 | return FALSE; | |
480 | ||
481 | while (1) | |
9ea19e54 | 482 | { |
d75aed2c DT |
483 | WCHAR long_name[MAX_PATH]; |
484 | WCHAR short_name[12]; | |
485 | ||
486 | r = (ioctl( fd, VFAT_IOCTL_READDIR_BOTH, (long)de ) != -1); | |
487 | if(!r) | |
488 | break; | |
489 | if (!de[0].d_reclen) | |
490 | break; | |
49a0224f | 491 | MultiByteToWideChar(CP_UNIXCP, 0, de[0].d_name, -1, long_name, MAX_PATH); |
d75aed2c DT |
492 | if (!DOSFS_ToDosFCBFormat( long_name, short_name )) |
493 | short_name[0] = '\0'; | |
494 | if (de[1].d_name[0]) | |
49a0224f | 495 | MultiByteToWideChar(CP_UNIXCP, 0, de[1].d_name, -1, long_name, MAX_PATH); |
9ea19e54 | 496 | else |
49a0224f | 497 | MultiByteToWideChar(CP_UNIXCP, 0, de[0].d_name, -1, long_name, MAX_PATH); |
d75aed2c DT |
498 | r = DOSFS_AddDirEntry(dir, long_name, short_name ); |
499 | if(!r) | |
500 | break; | |
9ea19e54 | 501 | } |
d75aed2c DT |
502 | if(r) |
503 | { | |
504 | static const WCHAR empty_strW[] = { 0 }; | |
505 | DOSFS_AddDirEntry(dir, empty_strW, empty_strW); | |
506 | } | |
507 | close(fd); | |
508 | return r; | |
509 | #else | |
510 | return FALSE; | |
9ea19e54 | 511 | #endif /* VFAT_IOCTL_READDIR_BOTH */ |
d75aed2c DT |
512 | } |
513 | ||
514 | ||
515 | /*********************************************************************** | |
516 | * DOSFS_OpenDir_Normal | |
517 | * | |
518 | * Now use the standard opendir/readdir interface | |
519 | */ | |
49a0224f | 520 | static BOOL DOSFS_OpenDir_Normal( DOS_DIR **dir, const char *unix_path ) |
d75aed2c DT |
521 | { |
522 | DIR *unixdir = opendir( unix_path ); | |
523 | BOOL r = TRUE; | |
524 | static const WCHAR empty_strW[] = { 0 }; | |
525 | ||
526 | if(!unixdir) | |
527 | return FALSE; | |
528 | while(1) | |
529 | { | |
530 | WCHAR long_name[MAX_PATH]; | |
531 | struct dirent *de = readdir(unixdir); | |
532 | ||
533 | if(!de) | |
534 | break; | |
49a0224f | 535 | MultiByteToWideChar(CP_UNIXCP, 0, de->d_name, -1, long_name, MAX_PATH); |
d75aed2c DT |
536 | r = DOSFS_AddDirEntry(dir, long_name, empty_strW); |
537 | if(!r) | |
538 | break; | |
539 | } | |
540 | if(r) | |
541 | DOSFS_AddDirEntry(dir, empty_strW, empty_strW); | |
542 | closedir(unixdir); | |
543 | return r; | |
544 | } | |
545 | ||
546 | /*********************************************************************** | |
547 | * DOSFS_OpenDir | |
548 | */ | |
49a0224f | 549 | static DOS_DIR *DOSFS_OpenDir( const char *unix_path ) |
d75aed2c DT |
550 | { |
551 | const int init_size = 0x100; | |
11d09a83 | 552 | DOS_DIR *dir = HeapAlloc( GetProcessHeap(), 0, sizeof(*dir) + init_size*sizeof (WCHAR)); |
d75aed2c DT |
553 | BOOL r; |
554 | ||
555 | TRACE("%s\n",debugstr_a(unix_path)); | |
556 | ||
557 | if (!dir) | |
558 | { | |
559 | SetLastError( ERROR_NOT_ENOUGH_MEMORY ); | |
560 | return NULL; | |
561 | } | |
562 | dir->used = 0; | |
563 | dir->size = init_size; | |
564 | ||
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 = "/"; | |
568 | ||
49a0224f | 569 | r = DOSFS_OpenDir_VFAT( &dir, unix_path); |
9ea19e54 | 570 | |
d75aed2c | 571 | if(!r) |
49a0224f | 572 | r = DOSFS_OpenDir_Normal( &dir, unix_path); |
9ea19e54 | 573 | |
d75aed2c | 574 | if(!r) |
9ea19e54 | 575 | { |
d75aed2c | 576 | HeapFree(GetProcessHeap(), 0, dir); |
9ea19e54 AJ |
577 | return NULL; |
578 | } | |
d75aed2c DT |
579 | dir->used = 0; |
580 | ||
9ea19e54 AJ |
581 | return dir; |
582 | } | |
583 | ||
584 | ||
585 | /*********************************************************************** | |
586 | * DOSFS_CloseDir | |
587 | */ | |
588 | static void DOSFS_CloseDir( DOS_DIR *dir ) | |
589 | { | |
90476d6b | 590 | HeapFree( GetProcessHeap(), 0, dir ); |
9ea19e54 AJ |
591 | } |
592 | ||
593 | ||
594 | /*********************************************************************** | |
595 | * DOSFS_ReadDir | |
596 | */ | |
d75aed2c DT |
597 | static BOOL DOSFS_ReadDir( DOS_DIR *dir, LPCWSTR *long_name, |
598 | LPCWSTR *short_name ) | |
9ea19e54 | 599 | { |
d75aed2c | 600 | LPCWSTR sn, ln; |
9ea19e54 | 601 | |
d75aed2c DT |
602 | if (!dir) |
603 | return FALSE; | |
604 | ||
605 | /* the long pathname is first */ | |
11d09a83 | 606 | ln = &dir->names[dir->used]; |
d75aed2c DT |
607 | if(ln[0]) |
608 | *long_name = ln; | |
609 | else | |
610 | return FALSE; | |
11d09a83 | 611 | dir->used += (strlenW(ln) + 1); |
d75aed2c DT |
612 | |
613 | /* followed by the short path name */ | |
11d09a83 | 614 | sn = &dir->names[dir->used]; |
d75aed2c DT |
615 | if(sn[0]) |
616 | *short_name = sn; | |
617 | else | |
618 | *short_name = NULL; | |
11d09a83 | 619 | dir->used += (strlenW(sn) + 1); |
4610c0a9 | 620 | |
9ea19e54 AJ |
621 | return TRUE; |
622 | } | |
623 | ||
624 | ||
4f8c37b4 AJ |
625 | /*********************************************************************** |
626 | * DOSFS_Hash | |
627 | * | |
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 '/'. | |
c6c09442 | 632 | * 'buffer' must be at least 13 characters long. |
4f8c37b4 | 633 | */ |
d75aed2c | 634 | static void DOSFS_Hash( LPCWSTR name, LPWSTR buffer, BOOL dir_format, |
a3960292 | 635 | BOOL ignore_case ) |
4f8c37b4 AJ |
636 | { |
637 | static const char invalid_chars[] = INVALID_DOS_CHARS "~."; | |
638 | static const char hash_chars[32] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345"; | |
639 | ||
d75aed2c DT |
640 | LPCWSTR p, ext; |
641 | LPWSTR dst; | |
4f8c37b4 AJ |
642 | unsigned short hash; |
643 | int i; | |
644 | ||
d75aed2c DT |
645 | if (dir_format) |
646 | { | |
647 | for(i = 0; i < 11; i++) buffer[i] = ' '; | |
648 | buffer[11] = 0; | |
649 | } | |
4f8c37b4 | 650 | |
1e37a181 | 651 | if (DOSFS_ValidDOSName( name, ignore_case )) |
4f8c37b4 AJ |
652 | { |
653 | /* Check for '.' and '..' */ | |
654 | if (*name == '.') | |
655 | { | |
656 | buffer[0] = '.'; | |
657 | if (!dir_format) buffer[1] = buffer[2] = '\0'; | |
658 | if (name[1] == '.') buffer[1] = '.'; | |
c6c09442 | 659 | return; |
4f8c37b4 AJ |
660 | } |
661 | ||
662 | /* Simply copy the name, converting to uppercase */ | |
663 | ||
664 | for (dst = buffer; !IS_END_OF_NAME(*name) && (*name != '.'); name++) | |
d75aed2c | 665 | *dst++ = toupperW(*name); |
4f8c37b4 AJ |
666 | if (*name == '.') |
667 | { | |
668 | if (dir_format) dst = buffer + 8; | |
669 | else *dst++ = '.'; | |
670 | for (name++; !IS_END_OF_NAME(*name); name++) | |
d75aed2c | 671 | *dst++ = toupperW(*name); |
4f8c37b4 AJ |
672 | } |
673 | if (!dir_format) *dst = '\0'; | |
c6c09442 AJ |
674 | return; |
675 | } | |
676 | ||
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... */ | |
680 | if (ignore_case) | |
681 | { | |
682 | for (p = name, hash = 0xbeef; !IS_END_OF_NAME(p[1]); p++) | |
d75aed2c DT |
683 | hash = (hash<<3) ^ (hash>>5) ^ tolowerW(*p) ^ (tolowerW(p[1]) << 8); |
684 | hash = (hash<<3) ^ (hash>>5) ^ tolowerW(*p); /* Last character */ | |
4f8c37b4 AJ |
685 | } |
686 | else | |
687 | { | |
c6c09442 AJ |
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 */ | |
691 | } | |
1e37a181 | 692 | |
c6c09442 AJ |
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 */ | |
4f8c37b4 | 698 | |
c6c09442 AJ |
699 | /* Copy first 4 chars, replacing invalid chars with '_' */ |
700 | for (i = 4, p = name, dst = buffer; i > 0; i--, p++) | |
701 | { | |
702 | if (IS_END_OF_NAME(*p) || (p == ext)) break; | |
d75aed2c | 703 | *dst++ = (*p < 256 && strchr( invalid_chars, (char)*p )) ? '_' : toupperW(*p); |
c6c09442 AJ |
704 | } |
705 | /* Pad to 5 chars with '~' */ | |
706 | while (i-- >= 0) *dst++ = '~'; | |
4f8c37b4 | 707 | |
c6c09442 AJ |
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]; | |
4f8c37b4 | 712 | |
c6c09442 AJ |
713 | /* Copy the first 3 chars of the extension (if any) */ |
714 | if (ext) | |
715 | { | |
716 | if (!dir_format) *dst++ = '.'; | |
717 | for (i = 3, ext++; (i > 0) && !IS_END_OF_NAME(*ext); i--, ext++) | |
d75aed2c | 718 | *dst++ = (*ext < 256 && strchr( invalid_chars, (char)*ext )) ? '_' : toupperW(*ext); |
4f8c37b4 | 719 | } |
c6c09442 | 720 | if (!dir_format) *dst = '\0'; |
4f8c37b4 AJ |
721 | } |
722 | ||
723 | ||
724 | /*********************************************************************** | |
725 | * DOSFS_FindUnixName | |
726 | * | |
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 '/'. | |
c6c09442 AJ |
730 | * Return TRUE if OK, FALSE if no file name matches. |
731 | * | |
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. | |
4f8c37b4 | 735 | */ |
d75aed2c DT |
736 | BOOL DOSFS_FindUnixName( const DOS_FULL_NAME *path, LPCWSTR name, char *long_buf, |
737 | INT long_len, LPWSTR short_buf, BOOL ignore_case) | |
4f8c37b4 | 738 | { |
9ea19e54 | 739 | DOS_DIR *dir; |
d75aed2c DT |
740 | LPCWSTR long_name, short_name; |
741 | WCHAR dos_name[12], tmp_buf[13]; | |
a3960292 | 742 | BOOL ret; |
4f8c37b4 | 743 | |
d75aed2c DT |
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 ); | |
bb9e66e2 DH |
747 | /* Ignore trailing dots and spaces */ |
748 | while (len > 1 && (name[len-1] == '.' || name[len-1] == ' ')) len--; | |
c6c09442 | 749 | if (long_len < len + 1) return FALSE; |
4f8c37b4 | 750 | |
d75aed2c | 751 | TRACE("%s,%s\n", path->long_name, debugstr_w(name) ); |
4f8c37b4 | 752 | |
c6c09442 | 753 | if (!DOSFS_ToDosFCBFormat( name, dos_name )) dos_name[0] = '\0'; |
9ea19e54 | 754 | |
49a0224f | 755 | if (!(dir = DOSFS_OpenDir( path->long_name ))) |
4f8c37b4 | 756 | { |
dd03cc19 | 757 | WARN("(%s,%s): can't open dir: %s\n", |
d75aed2c | 758 | path->long_name, debugstr_w(name), strerror(errno) ); |
c6c09442 | 759 | return FALSE; |
4f8c37b4 | 760 | } |
9ea19e54 AJ |
761 | |
762 | while ((ret = DOSFS_ReadDir( dir, &long_name, &short_name ))) | |
4f8c37b4 AJ |
763 | { |
764 | /* Check against Unix name */ | |
d75aed2c | 765 | if (len == strlenW(long_name)) |
1e37a181 | 766 | { |
c6c09442 | 767 | if (!ignore_case) |
1e37a181 | 768 | { |
d75aed2c | 769 | if (!strncmpW( long_name, name, len )) break; |
1e37a181 AJ |
770 | } |
771 | else | |
772 | { | |
d75aed2c | 773 | if (!strncmpiW( long_name, name, len )) break; |
1e37a181 AJ |
774 | } |
775 | } | |
9ea19e54 | 776 | if (dos_name[0]) |
4f8c37b4 AJ |
777 | { |
778 | /* Check against hashed DOS name */ | |
9ea19e54 | 779 | if (!short_name) |
c6c09442 AJ |
780 | { |
781 | DOSFS_Hash( long_name, tmp_buf, TRUE, ignore_case ); | |
782 | short_name = tmp_buf; | |
783 | } | |
d75aed2c | 784 | if (!strcmpW( dos_name, short_name )) break; |
4f8c37b4 AJ |
785 | } |
786 | } | |
c6c09442 AJ |
787 | if (ret) |
788 | { | |
49a0224f | 789 | if (long_buf) WideCharToMultiByte(CP_UNIXCP, 0, long_name, -1, long_buf, long_len, NULL, NULL); |
c6c09442 AJ |
790 | if (short_buf) |
791 | { | |
792 | if (short_name) | |
793 | DOSFS_ToDosDTAFormat( short_name, short_buf ); | |
794 | else | |
795 | DOSFS_Hash( long_name, short_buf, FALSE, ignore_case ); | |
796 | } | |
d75aed2c DT |
797 | TRACE("(%s,%s) -> %s (%s)\n", path->long_name, debugstr_w(name), |
798 | debugstr_w(long_name), short_buf ? debugstr_w(short_buf) : "***"); | |
c6c09442 AJ |
799 | } |
800 | else | |
d75aed2c | 801 | WARN("%s not found in '%s'\n", debugstr_w(name), path->long_name); |
9ea19e54 AJ |
802 | DOSFS_CloseDir( dir ); |
803 | return ret; | |
4f8c37b4 AJ |
804 | } |
805 | ||
806 | ||
807 | /*********************************************************************** | |
829fe323 | 808 | * DOSFS_GetDevice |
4f8c37b4 | 809 | * |
829fe323 | 810 | * Check if a DOS file name represents a DOS device and return the device. |
a11d7b1a | 811 | */ |
d75aed2c | 812 | const DOS_DEVICE *DOSFS_GetDevice( LPCWSTR name ) |
a11d7b1a | 813 | { |
267ca682 | 814 | unsigned int i; |
d75aed2c | 815 | const WCHAR *p; |
a11d7b1a | 816 | |
11d09a83 | 817 | if (!name) return NULL; /* if wine_server_handle_to_fd was used */ |
a11d7b1a | 818 | if (name[0] && (name[1] == ':')) name += 2; |
d75aed2c DT |
819 | if ((p = strrchrW( name, '/' ))) name = p + 1; |
820 | if ((p = strrchrW( name, '\\' ))) name = p + 1; | |
a11d7b1a AJ |
821 | for (i = 0; i < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0]); i++) |
822 | { | |
d75aed2c DT |
823 | const WCHAR *dev = DOSFS_Devices[i].name; |
824 | if (!strncmpiW( dev, name, strlenW(dev) )) | |
a11d7b1a | 825 | { |
d75aed2c | 826 | p = name + strlenW( dev ); |
e3178f9f | 827 | if (!*p || (*p == '.') || (*p == ':')) return &DOSFS_Devices[i]; |
a11d7b1a AJ |
828 | } |
829 | } | |
829fe323 | 830 | return NULL; |
a11d7b1a AJ |
831 | } |
832 | ||
62a8b433 AJ |
833 | |
834 | /*********************************************************************** | |
835 | * DOSFS_GetDeviceByHandle | |
836 | */ | |
267ca682 | 837 | const DOS_DEVICE *DOSFS_GetDeviceByHandle( HANDLE hFile ) |
62a8b433 | 838 | { |
92643003 | 839 | const DOS_DEVICE *ret = NULL; |
cf27a7fa | 840 | SERVER_START_REQ( get_device_id ) |
62a8b433 | 841 | { |
92643003 | 842 | req->handle = hFile; |
cf27a7fa | 843 | if (!wine_server_call( req )) |
92643003 | 844 | { |
cf27a7fa AJ |
845 | if ((reply->id >= 0) && |
846 | (reply->id < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0]))) | |
847 | ret = &DOSFS_Devices[reply->id]; | |
92643003 | 848 | } |
62a8b433 | 849 | } |
92643003 AJ |
850 | SERVER_END_REQ; |
851 | return ret; | |
62a8b433 AJ |
852 | } |
853 | ||
854 | ||
11776c1f MM |
855 | /************************************************************************** |
856 | * DOSFS_CreateCommPort | |
857 | */ | |
d75aed2c | 858 | static HANDLE DOSFS_CreateCommPort(LPCWSTR name, DWORD access, DWORD attributes, LPSECURITY_ATTRIBUTES sa) |
11776c1f | 859 | { |
8081e5a1 | 860 | HANDLE ret; |
e0deb0c6 AJ |
861 | HKEY hkey; |
862 | DWORD dummy; | |
863 | OBJECT_ATTRIBUTES attr; | |
864 | UNICODE_STRING nameW; | |
865 | WCHAR *devnameW; | |
866 | char tmp[128]; | |
11776c1f | 867 | char devname[40]; |
e0deb0c6 AJ |
868 | |
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}; | |
11776c1f | 874 | |
d75aed2c | 875 | TRACE_(file)("%s %lx %lx\n", debugstr_w(name), access, attributes); |
11776c1f | 876 | |
e0deb0c6 AJ |
877 | attr.Length = sizeof(attr); |
878 | attr.RootDirectory = 0; | |
879 | attr.ObjectName = &nameW; | |
880 | attr.Attributes = 0; | |
881 | attr.SecurityDescriptor = NULL; | |
882 | attr.SecurityQualityOfService = NULL; | |
883 | RtlInitUnicodeString( &nameW, serialportsW ); | |
884 | ||
885 | if (NtOpenKey( &hkey, KEY_ALL_ACCESS, &attr )) return 0; | |
11776c1f | 886 | |
e0deb0c6 AJ |
887 | RtlInitUnicodeString( &nameW, name ); |
888 | if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy )) | |
889 | devnameW = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data; | |
890 | else | |
891 | devnameW = NULL; | |
892 | ||
893 | NtClose( hkey ); | |
894 | ||
895 | if (!devnameW) return 0; | |
d75aed2c DT |
896 | WideCharToMultiByte(CP_ACP, 0, devnameW, -1, devname, sizeof(devname), NULL, NULL); |
897 | ||
898 | TRACE("opening %s as %s\n", devname, debugstr_w(name)); | |
11776c1f | 899 | |
9caa71ee | 900 | SERVER_START_REQ( create_serial ) |
57f05e19 | 901 | { |
57f05e19 | 902 | req->access = access; |
3bbeb72d | 903 | req->inherit = (sa && (sa->nLength>=sizeof(*sa)) && sa->bInheritHandle); |
568c67e1 | 904 | req->attributes = attributes; |
57f05e19 | 905 | req->sharing = FILE_SHARE_READ|FILE_SHARE_WRITE; |
9caa71ee | 906 | wine_server_add_data( req, devname, strlen(devname) ); |
57f05e19 | 907 | SetLastError(0); |
9caa71ee AJ |
908 | wine_server_call_err( req ); |
909 | ret = reply->handle; | |
57f05e19 | 910 | } |
9caa71ee | 911 | SERVER_END_REQ; |
57f05e19 | 912 | |
0a788579 | 913 | if(!ret) |
e15badb4 | 914 | ERR("Couldn't open device '%s' ! (check permissions)\n",devname); |
0a788579 | 915 | else |
ed800c69 | 916 | TRACE("return %p\n", ret ); |
57f05e19 | 917 | return ret; |
11776c1f MM |
918 | } |
919 | ||
a11d7b1a AJ |
920 | /*********************************************************************** |
921 | * DOSFS_OpenDevice | |
922 | * | |
923 | * Open a DOS device. This might not map 1:1 into the UNIX device concept. | |
8081e5a1 | 924 | * Returns 0 on failure. |
4f8c37b4 | 925 | */ |
d75aed2c | 926 | HANDLE DOSFS_OpenDevice( LPCWSTR name, DWORD access, DWORD attributes, LPSECURITY_ATTRIBUTES sa ) |
4f8c37b4 | 927 | { |
267ca682 | 928 | unsigned int i; |
d75aed2c | 929 | const WCHAR *p; |
8081e5a1 | 930 | HANDLE handle; |
4f8c37b4 | 931 | |
0c126c7c | 932 | if (name[0] && (name[1] == ':')) name += 2; |
d75aed2c DT |
933 | if ((p = strrchrW( name, '/' ))) name = p + 1; |
934 | if ((p = strrchrW( name, '\\' ))) name = p + 1; | |
4f8c37b4 AJ |
935 | for (i = 0; i < sizeof(DOSFS_Devices)/sizeof(DOSFS_Devices[0]); i++) |
936 | { | |
d75aed2c DT |
937 | const WCHAR *dev = DOSFS_Devices[i].name; |
938 | if (!strncmpiW( dev, name, strlenW(dev) )) | |
4f8c37b4 | 939 | { |
d75aed2c | 940 | p = name + strlenW( dev ); |
e3178f9f | 941 | if (!*p || (*p == '.') || (*p == ':')) { |
d75aed2c DT |
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}; | |
a11d7b1a | 947 | /* got it */ |
d75aed2c | 948 | if (!strcmpiW(DOSFS_Devices[i].name, nulW)) |
0562539d | 949 | return FILE_CreateFile( "/dev/null", access, |
3bbeb72d | 950 | FILE_SHARE_READ|FILE_SHARE_WRITE, sa, |
708a846a | 951 | OPEN_EXISTING, 0, 0, TRUE, DRIVE_UNKNOWN ); |
d75aed2c | 952 | if (!strcmpiW(DOSFS_Devices[i].name, conW)) { |
8081e5a1 | 953 | HANDLE to_dup; |
0562539d AJ |
954 | switch (access & (GENERIC_READ|GENERIC_WRITE)) { |
955 | case GENERIC_READ: | |
a0d77315 | 956 | to_dup = GetStdHandle( STD_INPUT_HANDLE ); |
a11d7b1a | 957 | break; |
0562539d | 958 | case GENERIC_WRITE: |
a0d77315 | 959 | to_dup = GetStdHandle( STD_OUTPUT_HANDLE ); |
a11d7b1a AJ |
960 | break; |
961 | default: | |
dd03cc19 | 962 | FIXME("can't open CON read/write\n"); |
8081e5a1 | 963 | return 0; |
a11d7b1a | 964 | } |
a0d77315 | 965 | if (!DuplicateHandle( GetCurrentProcess(), to_dup, GetCurrentProcess(), |
9a624916 VB |
966 | &handle, 0, |
967 | sa && (sa->nLength>=sizeof(*sa)) && sa->bInheritHandle, | |
3bbeb72d | 968 | DUPLICATE_SAME_ACCESS )) |
8081e5a1 | 969 | handle = 0; |
a0d77315 | 970 | return handle; |
a11d7b1a | 971 | } |
d75aed2c DT |
972 | if (!strcmpiW(DOSFS_Devices[i].name, scsimgrW) || |
973 | !strcmpiW(DOSFS_Devices[i].name, hpscanW) || | |
974 | !strcmpiW(DOSFS_Devices[i].name, emmxxxx0W)) | |
fbe63adc | 975 | { |
3bbeb72d | 976 | return FILE_CreateDevice( i, access, sa ); |
f90efa9c | 977 | } |
44b5bf59 | 978 | |
3bbeb72d | 979 | if( (handle=DOSFS_CreateCommPort(DOSFS_Devices[i].name,access,attributes,sa)) ) |
44b5bf59 | 980 | return handle; |
d75aed2c | 981 | FIXME("device open %s not supported (yet)\n", debugstr_w(DOSFS_Devices[i].name)); |
8081e5a1 | 982 | return 0; |
a11d7b1a | 983 | } |
4f8c37b4 AJ |
984 | } |
985 | } | |
8081e5a1 | 986 | return 0; |
4f8c37b4 AJ |
987 | } |
988 | ||
a11d7b1a | 989 | |
4f8c37b4 | 990 | /*********************************************************************** |
9ea19e54 | 991 | * DOSFS_GetPathDrive |
4f8c37b4 | 992 | * |
9ea19e54 | 993 | * Get the drive specified by a given path name (DOS or Unix format). |
4f8c37b4 | 994 | */ |
d75aed2c | 995 | static int DOSFS_GetPathDrive( LPCWSTR *name ) |
4f8c37b4 | 996 | { |
9ea19e54 | 997 | int drive; |
d75aed2c | 998 | LPCWSTR p = *name; |
4f8c37b4 | 999 | |
9ea19e54 | 1000 | if (*p && (p[1] == ':')) |
4f8c37b4 | 1001 | { |
d75aed2c | 1002 | drive = toupperW(*p) - 'A'; |
9ea19e54 | 1003 | *name += 2; |
4f8c37b4 | 1004 | } |
9ea19e54 | 1005 | else if (*p == '/') /* Absolute Unix path? */ |
4f8c37b4 | 1006 | { |
d75aed2c | 1007 | if ((drive = DRIVE_FindDriveRootW( name )) == -1) |
4f8c37b4 | 1008 | { |
d75aed2c | 1009 | MESSAGE("Warning: %s not accessible from a configured DOS drive\n", debugstr_w(*name) ); |
4f8c37b4 | 1010 | /* Assume it really was a DOS name */ |
9a624916 | 1011 | drive = DRIVE_GetCurrentDrive(); |
4f8c37b4 AJ |
1012 | } |
1013 | } | |
1014 | else drive = DRIVE_GetCurrentDrive(); | |
1015 | ||
1016 | if (!DRIVE_IsValid(drive)) | |
1017 | { | |
4ff2a27c | 1018 | SetLastError( ERROR_INVALID_DRIVE ); |
9ea19e54 | 1019 | return -1; |
4f8c37b4 | 1020 | } |
9ea19e54 AJ |
1021 | return drive; |
1022 | } | |
1023 | ||
1024 | ||
1025 | /*********************************************************************** | |
c6c09442 | 1026 | * DOSFS_GetFullName |
9ea19e54 | 1027 | * |
c6c09442 AJ |
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 | |
9ea19e54 | 1031 | * component is only checked if 'check_last' is non-zero. |
c6c09442 AJ |
1032 | * The buffers pointed to by 'long_buf' and 'short_buf' must be |
1033 | * at least MAX_PATHNAME_LEN long. | |
9ea19e54 | 1034 | */ |
d75aed2c | 1035 | BOOL DOSFS_GetFullName( LPCWSTR name, BOOL check_last, DOS_FULL_NAME *full ) |
9ea19e54 | 1036 | { |
a3960292 | 1037 | BOOL found; |
49a0224f | 1038 | UINT flags; |
d75aed2c DT |
1039 | char *p_l, *root; |
1040 | LPWSTR p_s; | |
1041 | static const WCHAR driveA_rootW[] = {'A',':','\\',0}; | |
1042 | static const WCHAR dos_rootW[] = {'\\',0}; | |
c6c09442 | 1043 | |
d75aed2c | 1044 | TRACE("%s (last=%d)\n", debugstr_w(name), check_last ); |
c6c09442 | 1045 | |
d52e1c4b GP |
1046 | if ((!*name) || (*name=='\n')) |
1047 | { /* error code for Win98 */ | |
1048 | SetLastError(ERROR_BAD_PATHNAME); | |
1049 | return FALSE; | |
1050 | } | |
1051 | ||
c6c09442 AJ |
1052 | if ((full->drive = DOSFS_GetPathDrive( &name )) == -1) return FALSE; |
1053 | flags = DRIVE_GetFlags( full->drive ); | |
9ea19e54 | 1054 | |
a3960292 | 1055 | lstrcpynA( full->long_name, DRIVE_GetRoot( full->drive ), |
c6c09442 AJ |
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 */ | |
9ea19e54 | 1059 | |
d75aed2c | 1060 | strcpyW( full->short_name, driveA_rootW ); |
c6c09442 | 1061 | full->short_name[0] += full->drive; |
4f8c37b4 | 1062 | |
c6c09442 | 1063 | if ((*name == '\\') || (*name == '/')) /* Absolute path */ |
4f8c37b4 AJ |
1064 | { |
1065 | while ((*name == '\\') || (*name == '/')) name++; | |
1066 | } | |
dcf0beac | 1067 | else /* Relative path */ |
4f8c37b4 | 1068 | { |
a3960292 | 1069 | lstrcpynA( root + 1, DRIVE_GetUnixCwd( full->drive ), |
c6c09442 | 1070 | sizeof(full->long_name) - (root - full->long_name) - 1 ); |
4f8c37b4 | 1071 | if (root[1]) *root = '/'; |
d75aed2c DT |
1072 | lstrcpynW( full->short_name + 3, DRIVE_GetDosCwd( full->drive ), |
1073 | sizeof(full->short_name)/sizeof(full->short_name[0]) - 3 ); | |
4f8c37b4 AJ |
1074 | } |
1075 | ||
c6c09442 AJ |
1076 | p_l = full->long_name[1] ? full->long_name + strlen(full->long_name) |
1077 | : full->long_name; | |
d75aed2c | 1078 | p_s = full->short_name[3] ? full->short_name + strlenW(full->short_name) |
c6c09442 | 1079 | : full->short_name + 2; |
9ea19e54 | 1080 | found = TRUE; |
c6c09442 | 1081 | |
4f8c37b4 AJ |
1082 | while (*name && found) |
1083 | { | |
c6c09442 AJ |
1084 | /* Check for '.' and '..' */ |
1085 | ||
1086 | if (*name == '.') | |
4f8c37b4 | 1087 | { |
c6c09442 AJ |
1088 | if (IS_END_OF_NAME(name[1])) |
1089 | { | |
1090 | name++; | |
1091 | while ((*name == '\\') || (*name == '/')) name++; | |
1092 | continue; | |
1093 | } | |
1094 | else if ((name[1] == '.') && IS_END_OF_NAME(name[2])) | |
1095 | { | |
1096 | 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 */ | |
1101 | continue; | |
1102 | } | |
4f8c37b4 | 1103 | } |
c6c09442 AJ |
1104 | |
1105 | /* Make sure buffers are large enough */ | |
1106 | ||
d75aed2c | 1107 | if ((p_s >= full->short_name + sizeof(full->short_name)/sizeof(full->short_name[0]) - 14) || |
c6c09442 | 1108 | (p_l >= full->long_name + sizeof(full->long_name) - 1)) |
4f8c37b4 | 1109 | { |
4ff2a27c | 1110 | SetLastError( ERROR_PATH_NOT_FOUND ); |
c6c09442 | 1111 | return FALSE; |
4f8c37b4 | 1112 | } |
c6c09442 AJ |
1113 | |
1114 | /* Get the long and short name matching the file name */ | |
1115 | ||
d75aed2c | 1116 | if ((found = DOSFS_FindUnixName( full, name, p_l + 1, |
c6c09442 AJ |
1117 | sizeof(full->long_name) - (p_l - full->long_name) - 1, |
1118 | p_s + 1, !(flags & DRIVE_CASE_SENSITIVE) ))) | |
4f8c37b4 | 1119 | { |
c6c09442 AJ |
1120 | *p_l++ = '/'; |
1121 | p_l += strlen(p_l); | |
1122 | *p_s++ = '\\'; | |
d75aed2c | 1123 | p_s += strlenW(p_s); |
4f8c37b4 AJ |
1124 | while (!IS_END_OF_NAME(*name)) name++; |
1125 | } | |
7e56f684 | 1126 | else if (!check_last) |
4f8c37b4 | 1127 | { |
c6c09442 AJ |
1128 | *p_l++ = '/'; |
1129 | *p_s++ = '\\'; | |
1130 | while (!IS_END_OF_NAME(*name) && | |
d75aed2c | 1131 | (p_s < full->short_name + sizeof(full->short_name)/sizeof(full->short_name[0]) - 1) && |
c6c09442 AJ |
1132 | (p_l < full->long_name + sizeof(full->long_name) - 1)) |
1133 | { | |
d75aed2c DT |
1134 | WCHAR wch; |
1135 | *p_s++ = tolowerW(*name); | |
829fe323 AJ |
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. */ | |
d75aed2c DT |
1139 | if (flags & DRIVE_CASE_SENSITIVE) wch = tolowerW(*name); |
1140 | else wch = *name; | |
49a0224f | 1141 | p_l += WideCharToMultiByte(CP_UNIXCP, 0, &wch, 1, p_l, 2, NULL, NULL); |
c6c09442 AJ |
1142 | name++; |
1143 | } | |
bb9e66e2 DH |
1144 | /* Ignore trailing dots and spaces */ |
1145 | while(p_l[-1] == '.' || p_l[-1] == ' ') { | |
1146 | --p_l; | |
1147 | --p_s; | |
1148 | } | |
d75aed2c DT |
1149 | *p_l = '\0'; |
1150 | *p_s = '\0'; | |
4f8c37b4 AJ |
1151 | } |
1152 | while ((*name == '\\') || (*name == '/')) name++; | |
1153 | } | |
c6c09442 | 1154 | |
4f8c37b4 AJ |
1155 | if (!found) |
1156 | { | |
2772a67c | 1157 | if (check_last) |
4f8c37b4 | 1158 | { |
4ff2a27c | 1159 | SetLastError( ERROR_FILE_NOT_FOUND ); |
c6c09442 | 1160 | return FALSE; |
4f8c37b4 | 1161 | } |
2772a67c | 1162 | if (*name) /* Not last */ |
4f8c37b4 | 1163 | { |
4ff2a27c | 1164 | SetLastError( ERROR_PATH_NOT_FOUND ); |
c6c09442 | 1165 | return FALSE; |
4f8c37b4 AJ |
1166 | } |
1167 | } | |
c6c09442 | 1168 | if (!full->long_name[0]) strcpy( full->long_name, "/" ); |
d75aed2c DT |
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) ); | |
c6c09442 | 1171 | return TRUE; |
4f8c37b4 AJ |
1172 | } |
1173 | ||
1174 | ||
3a0f8b79 | 1175 | /*********************************************************************** |
044855c6 | 1176 | * wine_get_unix_file_name (KERNEL32.@) Not a Windows API |
3a0f8b79 AJ |
1177 | * |
1178 | * Return the full Unix file name for a given path. | |
1179 | */ | |
5bf3a266 | 1180 | BOOL WINAPI wine_get_unix_file_name( LPCWSTR dosW, LPSTR buffer, DWORD len ) |
3a0f8b79 AJ |
1181 | { |
1182 | BOOL ret; | |
1183 | DOS_FULL_NAME path; | |
d75aed2c | 1184 | |
d75aed2c DT |
1185 | ret = DOSFS_GetFullName( dosW, FALSE, &path ); |
1186 | if (ret && len) | |
1187 | { | |
1188 | strncpy( buffer, path.long_name, len ); | |
1189 | buffer[len - 1] = 0; /* ensure 0 termination */ | |
1190 | } | |
3a0f8b79 AJ |
1191 | return ret; |
1192 | } | |
1193 | ||
1194 | ||
e0deb0c6 AJ |
1195 | /*********************************************************************** |
1196 | * get_show_dir_symlinks_option | |
1197 | */ | |
1198 | static BOOL get_show_dir_symlinks_option(void) | |
1199 | { | |
1200 | static const WCHAR WineW[] = {'M','a','c','h','i','n','e','\\', | |
1201 | 'S','o','f','t','w','a','r','e','\\', | |
1202 | 'W','i','n','e','\\','W','i','n','e','\\', | |
1203 | 'C','o','n','f','i','g','\\','W','i','n','e',0}; | |
1204 | static const WCHAR ShowDirSymlinksW[] = {'S','h','o','w','D','i','r','S','y','m','l','i','n','k','s',0}; | |
1205 | ||
1206 | char tmp[80]; | |
1207 | HKEY hkey; | |
1208 | DWORD dummy; | |
1209 | OBJECT_ATTRIBUTES attr; | |
1210 | UNICODE_STRING nameW; | |
1211 | BOOL ret = FALSE; | |
1212 | ||
1213 | attr.Length = sizeof(attr); | |
1214 | attr.RootDirectory = 0; | |
1215 | attr.ObjectName = &nameW; | |
1216 | attr.Attributes = 0; | |
1217 | attr.SecurityDescriptor = NULL; | |
1218 | attr.SecurityQualityOfService = NULL; | |
1219 | RtlInitUnicodeString( &nameW, WineW ); | |
1220 | ||
1221 | if (!NtOpenKey( &hkey, KEY_ALL_ACCESS, &attr )) | |
1222 | { | |
1223 | RtlInitUnicodeString( &nameW, ShowDirSymlinksW ); | |
1224 | if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy )) | |
1225 | { | |
1226 | WCHAR *str = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data; | |
1227 | ret = IS_OPTION_TRUE( str[0] ); | |
1228 | } | |
1229 | NtClose( hkey ); | |
1230 | } | |
1231 | return ret; | |
1232 | } | |
1233 | ||
1234 | ||
4f8c37b4 | 1235 | /*********************************************************************** |
85ed45e3 | 1236 | * DOSFS_FindNextEx |
4f8c37b4 | 1237 | */ |
d75aed2c | 1238 | static int DOSFS_FindNextEx( FIND_FIRST_INFO *info, WIN32_FIND_DATAW *entry ) |
4f8c37b4 | 1239 | { |
a3960292 | 1240 | UINT flags = DRIVE_GetFlags( info->drive ); |
85ed45e3 AJ |
1241 | char *p, buffer[MAX_PATHNAME_LEN]; |
1242 | const char *drive_path; | |
1243 | int drive_root; | |
d75aed2c | 1244 | LPCWSTR long_name, short_name; |
85ed45e3 | 1245 | BY_HANDLE_FILE_INFORMATION fileinfo; |
64e047fe | 1246 | BOOL is_symlink; |
1e37a181 | 1247 | |
85ed45e3 AJ |
1248 | drive_path = info->path + strlen(DRIVE_GetRoot( info->drive )); |
1249 | while ((*drive_path == '/') || (*drive_path == '\\')) drive_path++; | |
1250 | drive_root = !*drive_path; | |
1251 | ||
a3960292 | 1252 | lstrcpynA( buffer, info->path, sizeof(buffer) - 1 ); |
4f8c37b4 AJ |
1253 | strcat( buffer, "/" ); |
1254 | p = buffer + strlen(buffer); | |
4f8c37b4 | 1255 | |
963985b3 | 1256 | while (DOSFS_ReadDir( info->u.dos_dir, &long_name, &short_name )) |
4f8c37b4 | 1257 | { |
85ed45e3 | 1258 | info->cur_pos++; |
139a4b18 | 1259 | |
0c126c7c | 1260 | /* Don't return '.' and '..' in the root of the drive */ |
9ea19e54 AJ |
1261 | if (drive_root && (long_name[0] == '.') && |
1262 | (!long_name[1] || ((long_name[1] == '.') && !long_name[2]))) | |
1263 | continue; | |
4f8c37b4 | 1264 | |
139a4b18 AJ |
1265 | /* Check the long mask */ |
1266 | ||
4610c0a9 | 1267 | if (info->long_mask && *info->long_mask) |
139a4b18 | 1268 | { |
85ed45e3 | 1269 | if (!DOSFS_MatchLong( info->long_mask, long_name, |
139a4b18 AJ |
1270 | flags & DRIVE_CASE_SENSITIVE )) continue; |
1271 | } | |
1272 | ||
139a4b18 | 1273 | /* Check the file attributes */ |
49a0224f | 1274 | WideCharToMultiByte(CP_UNIXCP, 0, long_name, -1, |
d75aed2c | 1275 | p, sizeof(buffer) - (int)(p - buffer), NULL, NULL); |
64e047fe | 1276 | if (!FILE_Stat( buffer, &fileinfo, &is_symlink )) |
4f8c37b4 | 1277 | { |
dd03cc19 | 1278 | WARN("can't stat %s\n", buffer); |
4f8c37b4 AJ |
1279 | continue; |
1280 | } | |
64e047fe | 1281 | if (is_symlink && (fileinfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) |
220312e9 AM |
1282 | { |
1283 | static int show_dir_symlinks = -1; | |
1284 | if (show_dir_symlinks == -1) | |
e0deb0c6 | 1285 | show_dir_symlinks = get_show_dir_symlinks_option(); |
220312e9 AM |
1286 | if (!show_dir_symlinks) continue; |
1287 | } | |
1288 | ||
139a4b18 AJ |
1289 | /* We now have a matching entry; fill the result and return */ |
1290 | ||
85ed45e3 AJ |
1291 | entry->dwFileAttributes = fileinfo.dwFileAttributes; |
1292 | entry->ftCreationTime = fileinfo.ftCreationTime; | |
1293 | entry->ftLastAccessTime = fileinfo.ftLastAccessTime; | |
1294 | entry->ftLastWriteTime = fileinfo.ftLastWriteTime; | |
1295 | entry->nFileSizeHigh = fileinfo.nFileSizeHigh; | |
1296 | entry->nFileSizeLow = fileinfo.nFileSizeLow; | |
c6c09442 AJ |
1297 | |
1298 | if (short_name) | |
1299 | DOSFS_ToDosDTAFormat( short_name, entry->cAlternateFileName ); | |
1300 | else | |
1301 | DOSFS_Hash( long_name, entry->cAlternateFileName, FALSE, | |
1302 | !(flags & DRIVE_CASE_SENSITIVE) ); | |
1303 | ||
d75aed2c DT |
1304 | lstrcpynW( entry->cFileName, long_name, sizeof(entry->cFileName)/sizeof(entry->cFileName[0]) ); |
1305 | if (!(flags & DRIVE_CASE_PRESERVING)) strlwrW( entry->cFileName ); | |
dd03cc19 | 1306 | TRACE("returning %s (%s) %02lx %ld\n", |
d75aed2c | 1307 | debugstr_w(entry->cFileName), debugstr_w(entry->cAlternateFileName), |
dd03cc19 | 1308 | entry->dwFileAttributes, entry->nFileSizeLow ); |
85ed45e3 | 1309 | return 1; |
4f8c37b4 | 1310 | } |
4f8c37b4 AJ |
1311 | return 0; /* End of directory */ |
1312 | } | |
ca22b33d | 1313 | |
9ea19e54 | 1314 | /************************************************************************* |
d75aed2c | 1315 | * FindFirstFileExW (KERNEL32.@) |
9ea19e54 | 1316 | */ |
d75aed2c DT |
1317 | HANDLE WINAPI FindFirstFileExW( |
1318 | LPCWSTR lpFileName, | |
2250f12c JS |
1319 | FINDEX_INFO_LEVELS fInfoLevelId, |
1320 | LPVOID lpFindFileData, | |
1321 | FINDEX_SEARCH_OPS fSearchOp, | |
1322 | LPVOID lpSearchFilter, | |
1323 | DWORD dwAdditionalFlags) | |
9ea19e54 | 1324 | { |
9ea19e54 | 1325 | FIND_FIRST_INFO *info; |
9a624916 | 1326 | |
d75aed2c DT |
1327 | if (!lpFileName) |
1328 | { | |
1329 | SetLastError(ERROR_PATH_NOT_FOUND); | |
1330 | return INVALID_HANDLE_VALUE; | |
1331 | } | |
1332 | ||
2250f12c JS |
1333 | if ((fSearchOp != FindExSearchNameMatch) || (dwAdditionalFlags != 0)) |
1334 | { | |
1335 | FIXME("options not implemented 0x%08x 0x%08lx\n", fSearchOp, dwAdditionalFlags ); | |
1336 | return INVALID_HANDLE_VALUE; | |
1337 | } | |
9ea19e54 | 1338 | |
2250f12c | 1339 | switch(fInfoLevelId) |
9ea19e54 | 1340 | { |
2250f12c JS |
1341 | case FindExInfoStandard: |
1342 | { | |
d75aed2c DT |
1343 | WIN32_FIND_DATAW * data = (WIN32_FIND_DATAW *) lpFindFileData; |
1344 | char *p; | |
1345 | INT long_mask_len; | |
d75aed2c | 1346 | |
2250f12c | 1347 | data->dwReserved0 = data->dwReserved1 = 0x0; |
963985b3 MM |
1348 | if (lpFileName[0] == '\\' && lpFileName[1] == '\\') |
1349 | { | |
1350 | ERR("UNC path name\n"); | |
33854afb | 1351 | if (!(info = HeapAlloc( GetProcessHeap(), 0, sizeof(FIND_FIRST_INFO)))) break; |
963985b3 | 1352 | info->u.smb_dir = SMB_FindFirst(lpFileName); |
2a5c1460 | 1353 | if(!info->u.smb_dir) |
963985b3 | 1354 | { |
33854afb | 1355 | HeapFree(GetProcessHeap(), 0, info); |
963985b3 MM |
1356 | break; |
1357 | } | |
963985b3 | 1358 | info->drive = -1; |
33854afb | 1359 | RtlInitializeCriticalSection( &info->cs ); |
963985b3 MM |
1360 | } |
1361 | else | |
1362 | { | |
1363 | DOS_FULL_NAME full_name; | |
1364 | ||
0de21b65 UB |
1365 | if (lpFileName[0] && lpFileName[1] == ':') |
1366 | { | |
1367 | /* don't allow root directories */ | |
1368 | if (!lpFileName[2] || | |
1369 | ((lpFileName[2] == '/' || lpFileName[2] == '\\') && !lpFileName[3])) | |
1370 | { | |
1371 | SetLastError(ERROR_FILE_NOT_FOUND); | |
1372 | return INVALID_HANDLE_VALUE; | |
1373 | } | |
1374 | } | |
d75aed2c | 1375 | if (!DOSFS_GetFullName( lpFileName, FALSE, &full_name )) break; |
33854afb AP |
1376 | if (!(info = HeapAlloc( GetProcessHeap(), 0, sizeof(FIND_FIRST_INFO)))) break; |
1377 | RtlInitializeCriticalSection( &info->cs ); | |
d75aed2c DT |
1378 | info->path = HeapAlloc( GetProcessHeap(), 0, strlen(full_name.long_name)+1 ); |
1379 | strcpy( info->path, full_name.long_name ); | |
1380 | ||
d75aed2c DT |
1381 | p = strrchr( info->path, '/' ); |
1382 | *p++ = '\0'; | |
49a0224f | 1383 | long_mask_len = MultiByteToWideChar(CP_UNIXCP, 0, p, -1, NULL, 0); |
d75aed2c | 1384 | info->long_mask = HeapAlloc( GetProcessHeap(), 0, long_mask_len * sizeof(WCHAR) ); |
49a0224f | 1385 | MultiByteToWideChar(CP_UNIXCP, 0, p, -1, info->long_mask, long_mask_len); |
d75aed2c | 1386 | |
d75aed2c DT |
1387 | info->drive = full_name.drive; |
1388 | info->cur_pos = 0; | |
1389 | ||
49a0224f | 1390 | info->u.dos_dir = DOSFS_OpenDir( info->path ); |
963985b3 | 1391 | } |
33854afb | 1392 | if (!FindNextFileW( (HANDLE) info, data )) |
2250f12c | 1393 | { |
33854afb | 1394 | FindClose( (HANDLE) info ); |
1fc2142a | 1395 | SetLastError( ERROR_FILE_NOT_FOUND ); |
2250f12c JS |
1396 | break; |
1397 | } | |
33854afb | 1398 | return (HANDLE) info; |
2250f12c JS |
1399 | } |
1400 | break; | |
1401 | default: | |
1402 | FIXME("fInfoLevelId 0x%08x not implemented\n", fInfoLevelId ); | |
9ea19e54 | 1403 | } |
2250f12c | 1404 | return INVALID_HANDLE_VALUE; |
9ea19e54 AJ |
1405 | } |
1406 | ||
9ea19e54 | 1407 | /************************************************************************* |
dae8de69 | 1408 | * FindFirstFileA (KERNEL32.@) |
9ea19e54 | 1409 | */ |
2250f12c JS |
1410 | HANDLE WINAPI FindFirstFileA( |
1411 | LPCSTR lpFileName, | |
1412 | WIN32_FIND_DATAA *lpFindData ) | |
9ea19e54 | 1413 | { |
2250f12c JS |
1414 | return FindFirstFileExA(lpFileName, FindExInfoStandard, lpFindData, |
1415 | FindExSearchNameMatch, NULL, 0); | |
9ea19e54 AJ |
1416 | } |
1417 | ||
9ea19e54 | 1418 | /************************************************************************* |
d75aed2c | 1419 | * FindFirstFileExA (KERNEL32.@) |
9ea19e54 | 1420 | */ |
d75aed2c DT |
1421 | HANDLE WINAPI FindFirstFileExA( |
1422 | LPCSTR lpFileName, | |
2250f12c JS |
1423 | FINDEX_INFO_LEVELS fInfoLevelId, |
1424 | LPVOID lpFindFileData, | |
1425 | FINDEX_SEARCH_OPS fSearchOp, | |
1426 | LPVOID lpSearchFilter, | |
1427 | DWORD dwAdditionalFlags) | |
9ea19e54 | 1428 | { |
2250f12c | 1429 | HANDLE handle; |
d75aed2c DT |
1430 | WIN32_FIND_DATAA *dataA; |
1431 | WIN32_FIND_DATAW dataW; | |
1432 | UNICODE_STRING pathW; | |
2250f12c | 1433 | |
d75aed2c | 1434 | if (!lpFileName) |
2250f12c | 1435 | { |
d75aed2c DT |
1436 | SetLastError(ERROR_PATH_NOT_FOUND); |
1437 | return INVALID_HANDLE_VALUE; | |
2250f12c JS |
1438 | } |
1439 | ||
d75aed2c | 1440 | if (!RtlCreateUnicodeStringFromAsciiz(&pathW, lpFileName)) |
9ea19e54 | 1441 | { |
d75aed2c | 1442 | SetLastError(ERROR_NOT_ENOUGH_MEMORY); |
2250f12c | 1443 | return INVALID_HANDLE_VALUE; |
9ea19e54 | 1444 | } |
d75aed2c DT |
1445 | |
1446 | handle = FindFirstFileExW(pathW.Buffer, fInfoLevelId, &dataW, fSearchOp, lpSearchFilter, dwAdditionalFlags); | |
1447 | RtlFreeUnicodeString(&pathW); | |
1448 | if (handle == INVALID_HANDLE_VALUE) return handle; | |
1449 | ||
1450 | dataA = (WIN32_FIND_DATAA *) lpFindFileData; | |
1451 | dataA->dwFileAttributes = dataW.dwFileAttributes; | |
1452 | dataA->ftCreationTime = dataW.ftCreationTime; | |
1453 | dataA->ftLastAccessTime = dataW.ftLastAccessTime; | |
1454 | dataA->ftLastWriteTime = dataW.ftLastWriteTime; | |
1455 | dataA->nFileSizeHigh = dataW.nFileSizeHigh; | |
1456 | dataA->nFileSizeLow = dataW.nFileSizeLow; | |
1457 | WideCharToMultiByte( CP_ACP, 0, dataW.cFileName, -1, | |
1458 | dataA->cFileName, sizeof(dataA->cFileName), NULL, NULL ); | |
1459 | WideCharToMultiByte( CP_ACP, 0, dataW.cAlternateFileName, -1, | |
1460 | dataA->cAlternateFileName, sizeof(dataA->cAlternateFileName), NULL, NULL ); | |
9ea19e54 AJ |
1461 | return handle; |
1462 | } | |
1463 | ||
2250f12c | 1464 | /************************************************************************* |
dae8de69 | 1465 | * FindFirstFileW (KERNEL32.@) |
2250f12c JS |
1466 | */ |
1467 | HANDLE WINAPI FindFirstFileW( LPCWSTR lpFileName, WIN32_FIND_DATAW *lpFindData ) | |
1468 | { | |
1469 | return FindFirstFileExW(lpFileName, FindExInfoStandard, lpFindData, | |
1470 | FindExSearchNameMatch, NULL, 0); | |
1471 | } | |
9ea19e54 AJ |
1472 | |
1473 | /************************************************************************* | |
d75aed2c | 1474 | * FindNextFileW (KERNEL32.@) |
9ea19e54 | 1475 | */ |
d75aed2c | 1476 | BOOL WINAPI FindNextFileW( HANDLE handle, WIN32_FIND_DATAW *data ) |
9ea19e54 AJ |
1477 | { |
1478 | FIND_FIRST_INFO *info; | |
963985b3 | 1479 | BOOL ret = FALSE; |
e1db8bd0 | 1480 | DWORD gle = ERROR_NO_MORE_FILES; |
9ea19e54 | 1481 | |
33854afb | 1482 | if (handle == INVALID_HANDLE_VALUE) |
9ea19e54 | 1483 | { |
4ff2a27c | 1484 | SetLastError( ERROR_INVALID_HANDLE ); |
963985b3 | 1485 | return ret; |
9ea19e54 | 1486 | } |
33854afb AP |
1487 | info = (FIND_FIRST_INFO*) handle; |
1488 | RtlEnterCriticalSection( &info->cs ); | |
963985b3 MM |
1489 | if (info->drive == -1) |
1490 | { | |
1491 | ret = SMB_FindNext( info->u.smb_dir, data ); | |
1492 | if(!ret) | |
1493 | { | |
1494 | SMB_CloseDir( info->u.smb_dir ); | |
1495 | HeapFree( GetProcessHeap(), 0, info->path ); | |
963985b3 MM |
1496 | } |
1497 | goto done; | |
1498 | } | |
1499 | else if (!info->path || !info->u.dos_dir) | |
9ea19e54 | 1500 | { |
e1db8bd0 | 1501 | goto done; |
9ea19e54 | 1502 | } |
963985b3 | 1503 | else if (!DOSFS_FindNextEx( info, data )) |
9ea19e54 | 1504 | { |
963985b3 | 1505 | DOSFS_CloseDir( info->u.dos_dir ); info->u.dos_dir = NULL; |
90476d6b | 1506 | HeapFree( GetProcessHeap(), 0, info->path ); |
d75aed2c DT |
1507 | info->path = NULL; |
1508 | HeapFree( GetProcessHeap(), 0, info->long_mask ); | |
1509 | info->long_mask = NULL; | |
1510 | goto done; | |
9ea19e54 | 1511 | } |
d75aed2c | 1512 | ret = TRUE; |
963985b3 | 1513 | done: |
33854afb | 1514 | RtlLeaveCriticalSection( &info->cs ); |
e1db8bd0 | 1515 | if( !ret ) SetLastError( gle ); |
963985b3 | 1516 | return ret; |
9ea19e54 AJ |
1517 | } |
1518 | ||
1519 | ||
9ea19e54 | 1520 | /************************************************************************* |
d75aed2c | 1521 | * FindNextFileA (KERNEL32.@) |
9ea19e54 | 1522 | */ |
d75aed2c | 1523 | BOOL WINAPI FindNextFileA( HANDLE handle, WIN32_FIND_DATAA *data ) |
9ea19e54 | 1524 | { |
d75aed2c DT |
1525 | WIN32_FIND_DATAW dataW; |
1526 | if (!FindNextFileW( handle, &dataW )) return FALSE; | |
1527 | data->dwFileAttributes = dataW.dwFileAttributes; | |
1528 | data->ftCreationTime = dataW.ftCreationTime; | |
1529 | data->ftLastAccessTime = dataW.ftLastAccessTime; | |
1530 | data->ftLastWriteTime = dataW.ftLastWriteTime; | |
1531 | data->nFileSizeHigh = dataW.nFileSizeHigh; | |
1532 | data->nFileSizeLow = dataW.nFileSizeLow; | |
1533 | WideCharToMultiByte( CP_ACP, 0, dataW.cFileName, -1, | |
1534 | data->cFileName, sizeof(data->cFileName), NULL, NULL ); | |
1535 | WideCharToMultiByte( CP_ACP, 0, dataW.cAlternateFileName, -1, | |
072dfb57 | 1536 | data->cAlternateFileName, |
d75aed2c | 1537 | sizeof(data->cAlternateFileName), NULL, NULL ); |
9ea19e54 AJ |
1538 | return TRUE; |
1539 | } | |
1540 | ||
9ea19e54 | 1541 | /************************************************************************* |
dae8de69 | 1542 | * FindClose (KERNEL32.@) |
9ea19e54 | 1543 | */ |
2250f12c | 1544 | BOOL WINAPI FindClose( HANDLE handle ) |
9ea19e54 | 1545 | { |
33854afb | 1546 | FIND_FIRST_INFO *info = (FIND_FIRST_INFO*) handle; |
9ea19e54 | 1547 | |
77fbbcf3 MM |
1548 | if (handle == INVALID_HANDLE_VALUE) goto error; |
1549 | ||
4f46b5de DS |
1550 | __TRY |
1551 | { | |
33854afb AP |
1552 | RtlEnterCriticalSection( &info->cs ); |
1553 | if (info) | |
77fbbcf3 | 1554 | { |
963985b3 | 1555 | if (info->u.dos_dir) DOSFS_CloseDir( info->u.dos_dir ); |
77fbbcf3 | 1556 | if (info->path) HeapFree( GetProcessHeap(), 0, info->path ); |
d75aed2c | 1557 | if (info->long_mask) HeapFree( GetProcessHeap(), 0, info->long_mask ); |
77fbbcf3 | 1558 | } |
4f46b5de DS |
1559 | } |
1560 | __EXCEPT(page_fault) | |
1561 | { | |
ed800c69 | 1562 | WARN("Illegal handle %p\n", handle); |
4f46b5de DS |
1563 | SetLastError( ERROR_INVALID_HANDLE ); |
1564 | return FALSE; | |
1565 | } | |
1566 | __ENDTRY | |
77fbbcf3 | 1567 | if (!info) goto error; |
33854afb AP |
1568 | RtlLeaveCriticalSection( &info->cs ); |
1569 | RtlDeleteCriticalSection( &info->cs ); | |
1570 | HeapFree(GetProcessHeap(), 0, info); | |
9ea19e54 | 1571 | return TRUE; |
77fbbcf3 MM |
1572 | |
1573 | error: | |
1574 | SetLastError( ERROR_INVALID_HANDLE ); | |
1575 | return FALSE; | |
9ea19e54 AJ |
1576 | } |
1577 | ||
15467bfb | 1578 | /*********************************************************************** |
dae8de69 | 1579 | * MulDiv (KERNEL32.@) |
15467bfb AJ |
1580 | * RETURNS |
1581 | * Result of multiplication and division | |
1582 | * -1: Overflow occurred or Divisor was 0 | |
1583 | */ | |
1584 | INT WINAPI MulDiv( | |
9a624916 | 1585 | INT nMultiplicand, |
15467bfb AJ |
1586 | INT nMultiplier, |
1587 | INT nDivisor) | |
1588 | { | |
1589 | #if SIZEOF_LONG_LONG >= 8 | |
1590 | long long ret; | |
1591 | ||
1592 | if (!nDivisor) return -1; | |
1593 | ||
1594 | /* We want to deal with a positive divisor to simplify the logic. */ | |
1595 | if (nDivisor < 0) | |
1596 | { | |
1597 | nMultiplicand = - nMultiplicand; | |
1598 | nDivisor = -nDivisor; | |
1599 | } | |
1600 | ||
1601 | /* If the result is positive, we "add" to round. else, we subtract to round. */ | |
1602 | if ( ( (nMultiplicand < 0) && (nMultiplier < 0) ) || | |
1603 | ( (nMultiplicand >= 0) && (nMultiplier >= 0) ) ) | |
1604 | ret = (((long long)nMultiplicand * nMultiplier) + (nDivisor/2)) / nDivisor; | |
1605 | else | |
1606 | ret = (((long long)nMultiplicand * nMultiplier) - (nDivisor/2)) / nDivisor; | |
1607 | ||
1608 | if ((ret > 2147483647) || (ret < -2147483647)) return -1; | |
1609 | return ret; | |
1610 | #else | |
1611 | if (!nDivisor) return -1; | |
1612 | ||
1613 | /* We want to deal with a positive divisor to simplify the logic. */ | |
1614 | if (nDivisor < 0) | |
1615 | { | |
1616 | nMultiplicand = - nMultiplicand; | |
1617 | nDivisor = -nDivisor; | |
1618 | } | |
1619 | ||
1620 | /* If the result is positive, we "add" to round. else, we subtract to round. */ | |
1621 | if ( ( (nMultiplicand < 0) && (nMultiplier < 0) ) || | |
1622 | ( (nMultiplicand >= 0) && (nMultiplier >= 0) ) ) | |
1623 | return ((nMultiplicand * nMultiplier) + (nDivisor/2)) / nDivisor; | |
9a624916 | 1624 | |
15467bfb | 1625 | return ((nMultiplicand * nMultiplier) - (nDivisor/2)) / nDivisor; |
9a624916 | 1626 | |
15467bfb AJ |
1627 | #endif |
1628 | } | |
1629 | ||
1630 | ||
75d86e1f | 1631 | /*********************************************************************** |
dae8de69 | 1632 | * DosDateTimeToFileTime (KERNEL32.@) |
75d86e1f | 1633 | */ |
a3960292 | 1634 | BOOL WINAPI DosDateTimeToFileTime( WORD fatdate, WORD fattime, LPFILETIME ft) |
75d86e1f | 1635 | { |
c6c09442 | 1636 | struct tm newtm; |
a0322773 VB |
1637 | #ifndef HAVE_TIMEGM |
1638 | struct tm *gtm; | |
1639 | time_t time1, time2; | |
1640 | #endif | |
c6c09442 AJ |
1641 | |
1642 | newtm.tm_sec = (fattime & 0x1f) * 2; | |
1643 | newtm.tm_min = (fattime >> 5) & 0x3f; | |
1644 | newtm.tm_hour = (fattime >> 11); | |
1645 | newtm.tm_mday = (fatdate & 0x1f); | |
1646 | newtm.tm_mon = ((fatdate >> 5) & 0x0f) - 1; | |
1647 | newtm.tm_year = (fatdate >> 9) + 80; | |
a0322773 | 1648 | #ifdef HAVE_TIMEGM |
1668870f | 1649 | RtlSecondsSince1970ToTime( timegm(&newtm), (LARGE_INTEGER *)ft ); |
a0322773 VB |
1650 | #else |
1651 | time1 = mktime(&newtm); | |
1652 | gtm = gmtime(&time1); | |
1653 | time2 = mktime(gtm); | |
1668870f | 1654 | RtlSecondsSince1970ToTime( 2*time1-time2, (LARGE_INTEGER *)ft ); |
a0322773 | 1655 | #endif |
75d86e1f AJ |
1656 | return TRUE; |
1657 | } | |
1658 | ||
1659 | ||
1660 | /*********************************************************************** | |
dae8de69 | 1661 | * FileTimeToDosDateTime (KERNEL32.@) |
75d86e1f | 1662 | */ |
a3960292 | 1663 | BOOL WINAPI FileTimeToDosDateTime( const FILETIME *ft, LPWORD fatdate, |
670cdc45 | 1664 | LPWORD fattime ) |
75d86e1f | 1665 | { |
f3d2a8d4 EP |
1666 | LARGE_INTEGER li; |
1667 | ULONG t; | |
1668 | time_t unixtime; | |
1669 | struct tm* tm; | |
1670 | ||
399901e0 GG |
1671 | li.u.LowPart = ft->dwLowDateTime; |
1672 | li.u.HighPart = ft->dwHighDateTime; | |
f3d2a8d4 EP |
1673 | RtlTimeToSecondsSince1970( &li, &t ); |
1674 | unixtime = t; | |
1675 | tm = gmtime( &unixtime ); | |
c6c09442 AJ |
1676 | if (fattime) |
1677 | *fattime = (tm->tm_hour << 11) + (tm->tm_min << 5) + (tm->tm_sec / 2); | |
1678 | if (fatdate) | |
1679 | *fatdate = ((tm->tm_year - 80) << 9) + ((tm->tm_mon + 1) << 5) | |
1680 | + tm->tm_mday; | |
75d86e1f AJ |
1681 | return TRUE; |
1682 | } | |
1683 | ||
75d86e1f | 1684 | |
c6c09442 | 1685 | /*********************************************************************** |
dae8de69 | 1686 | * QueryDosDeviceA (KERNEL32.@) |
c6c09442 AJ |
1687 | * |
1688 | * returns array of strings terminated by \0, terminated by \0 | |
1689 | */ | |
a3960292 | 1690 | DWORD WINAPI QueryDosDeviceA(LPCSTR devname,LPSTR target,DWORD bufsize) |
c6c09442 | 1691 | { |
a98f1297 SL |
1692 | DWORD ret = 0, retW; |
1693 | LPWSTR targetW = (LPWSTR)HeapAlloc(GetProcessHeap(),0, | |
1694 | bufsize * sizeof(WCHAR)); | |
1695 | UNICODE_STRING devnameW; | |
1696 | ||
1697 | if(devname) RtlCreateUnicodeStringFromAsciiz(&devnameW, devname); | |
1698 | else devnameW.Buffer = NULL; | |
1699 | ||
1700 | retW = QueryDosDeviceW(devnameW.Buffer, targetW, bufsize); | |
1701 | ||
1702 | ret = WideCharToMultiByte(CP_ACP, 0, targetW, retW, target, | |
1703 | bufsize, NULL, NULL); | |
1704 | ||
1705 | RtlFreeUnicodeString(&devnameW); | |
1706 | if (targetW) HeapFree(GetProcessHeap(),0,targetW); | |
1707 | return ret; | |
1708 | } | |
1709 | ||
1710 | ||
1711 | /*********************************************************************** | |
1712 | * QueryDosDeviceW (KERNEL32.@) | |
1713 | * | |
1714 | * returns array of strings terminated by \0, terminated by \0 | |
1715 | * | |
1716 | * FIXME | |
1717 | * - Win9x returns for all calls ERROR_INVALID_PARAMETER | |
1718 | * - the returned devices for devname == NULL is far from complete | |
1719 | * - its not checked that the returned device exist | |
1720 | */ | |
1721 | DWORD WINAPI QueryDosDeviceW(LPCWSTR devname,LPWSTR target,DWORD bufsize) | |
1722 | { | |
1723 | const WCHAR *pDev, *pName, *pNum = NULL; | |
1724 | int numsiz=0; | |
1725 | DWORD ret; | |
c6c09442 | 1726 | |
a98f1297 | 1727 | TRACE("(%s,...)\n", debugstr_w(devname)); |
c6c09442 AJ |
1728 | if (!devname) { |
1729 | /* return known MSDOS devices */ | |
a98f1297 SL |
1730 | DWORD ret = 0; |
1731 | int i; | |
1732 | static const WCHAR devices[][5] = {{'A','U','X',0}, | |
1733 | {'C','O','M','1',0}, | |
1734 | {'C','O','M','2',0}, | |
1735 | {'L','P','T','1',0}, | |
1736 | {'N','U','L',0,}}; | |
1737 | for(i=0; (i< (sizeof(devices)/sizeof(devices[0]))); i++) { | |
1738 | DWORD len = strlenW(devices[i]); | |
1739 | if(target && (bufsize >= ret + len + 2)) { | |
65e7196f | 1740 | strcpyW(target+ret, devices[i]); |
a98f1297 SL |
1741 | ret += len + 1; |
1742 | } else { | |
1743 | /* in this case WinXP returns 0 */ | |
1744 | FIXME("function return is wrong for WinXP!\n"); | |
1745 | SetLastError(ERROR_INSUFFICIENT_BUFFER); | |
1746 | break; | |
1747 | } | |
1748 | } | |
1749 | /* append drives here */ | |
1750 | if(target && bufsize > 0) target[ret++] = 0; | |
1751 | FIXME("Returned list is not complete\n"); | |
1752 | return ret; | |
c6c09442 | 1753 | } |
04d5efdf MM |
1754 | /* In theory all that are possible and have been defined. |
1755 | * Now just those below, since mirc uses it to check for special files. | |
1756 | * | |
9a624916 | 1757 | * (It is more complex, and supports netmounted stuff, and \\.\ stuff, |
04d5efdf MM |
1758 | * but currently we just ignore that.) |
1759 | */ | |
a98f1297 SL |
1760 | if (!strcmpiW(devname, auxW)) { |
1761 | pDev = dosW; | |
1762 | pName = comW; | |
1763 | numsiz = 1; | |
1764 | pNum = oneW; | |
1765 | } else if (!strcmpiW(devname, nulW)) { | |
1766 | pDev = devW; | |
1767 | pName = nullW; | |
1768 | } else if (!strncmpiW(devname, comW, strlenW(comW))) { | |
1769 | pDev = devW; | |
1770 | pName = serW; | |
1771 | pNum = devname + strlenW(comW); | |
1772 | for(numsiz=0; isdigitW(*(pNum+numsiz)); numsiz++); | |
1773 | if(*(pNum + numsiz)) { | |
1774 | SetLastError(ERROR_FILE_NOT_FOUND); | |
1775 | return 0; | |
04d5efdf | 1776 | } |
a98f1297 SL |
1777 | } else if (!strncmpiW(devname, lptW, strlenW(lptW))) { |
1778 | pDev = devW; | |
1779 | pName = parW; | |
1780 | pNum = devname + strlenW(lptW); | |
1781 | for(numsiz=0; isdigitW(*(pNum+numsiz)); numsiz++); | |
1782 | if(*(pNum + numsiz)) { | |
1783 | SetLastError(ERROR_FILE_NOT_FOUND); | |
04d5efdf MM |
1784 | return 0; |
1785 | } | |
a98f1297 SL |
1786 | } else { |
1787 | /* This might be a DOS device we do not handle yet ... */ | |
1788 | FIXME("(%s) not detected as DOS device!\n",debugstr_w(devname)); | |
04d5efdf | 1789 | |
a98f1297 SL |
1790 | /* Win9x set the error ERROR_INVALID_PARAMETER */ |
1791 | SetLastError(ERROR_FILE_NOT_FOUND); | |
1792 | return 0; | |
c6c09442 | 1793 | } |
a98f1297 | 1794 | FIXME("device %s may not exist on this computer\n", debugstr_w(devname)); |
c6c09442 | 1795 | |
a98f1297 SL |
1796 | ret = strlenW(pDev) + strlenW(pName) + numsiz + 2; |
1797 | if (ret > bufsize) ret = 0; | |
1798 | if (target && ret) { | |
65e7196f AJ |
1799 | strcpyW(target,pDev); |
1800 | strcatW(target,pName); | |
1801 | if (pNum) strcatW(target,pNum); | |
a98f1297 SL |
1802 | target[ret-1] = 0; |
1803 | } | |
c6c09442 AJ |
1804 | return ret; |
1805 | } |