1 | // Licensed to the .NET Foundation under one or more agreements. |
2 | // The .NET Foundation licenses this file to you under the MIT license. |
3 | // See the LICENSE file in the project root for more information. |
4 | |
5 | /*++ |
6 | |
7 | |
8 | |
9 | Module Name: |
10 | |
11 | path.c |
12 | |
13 | Abstract: |
14 | |
15 | Implementation of all functions related to path support |
16 | |
17 | Revision History: |
18 | |
19 | |
20 | |
21 | --*/ |
22 | |
23 | #include "pal/thread.hpp" |
24 | #include "pal/palinternal.h" |
25 | #include "pal/dbgmsg.h" |
26 | #include "pal/file.h" |
27 | #include "pal/malloc.hpp" |
28 | #include "pal/stackstring.hpp" |
29 | |
30 | #include <errno.h> |
31 | |
32 | #include <unistd.h> |
33 | #include <stdlib.h> |
34 | |
35 | SET_DEFAULT_DEBUG_CHANNEL(FILE); |
36 | |
37 | |
38 | // In safemath.h, Template SafeInt uses macro _ASSERTE, which need to use variable |
39 | // defdbgchan defined by SET_DEFAULT_DEBUG_CHANNEL. Therefore, the include statement |
40 | // should be placed after the SET_DEFAULT_DEBUG_CHANNEL(FILE) |
41 | #include <safemath.h> |
42 | |
43 | int MaxWCharToAcpLengthRatio = 3; |
44 | /*++ |
45 | Function: |
46 | GetFullPathNameA |
47 | |
48 | See MSDN doc. |
49 | --*/ |
50 | DWORD |
51 | PALAPI |
52 | GetFullPathNameA( |
53 | IN LPCSTR lpFileName, |
54 | IN DWORD nBufferLength, |
55 | OUT LPSTR lpBuffer, |
56 | OUT LPSTR *lpFilePart) |
57 | { |
58 | DWORD nReqPathLen, nRet = 0; |
59 | PathCharString unixPath; |
60 | LPSTR unixPathBuf; |
61 | BOOL fullPath = FALSE; |
62 | |
63 | PERF_ENTRY(GetFullPathNameA); |
64 | ENTRY("GetFullPathNameA(lpFileName=%p (%s), nBufferLength=%u, lpBuffer=%p, " |
65 | "lpFilePart=%p)\n" , |
66 | lpFileName?lpFileName:"NULL" , |
67 | lpFileName?lpFileName:"NULL" , nBufferLength, lpBuffer, lpFilePart); |
68 | |
69 | if(NULL == lpFileName) |
70 | { |
71 | WARN("lpFileName is NULL\n" ); |
72 | SetLastError(ERROR_INVALID_PARAMETER); |
73 | goto done; |
74 | } |
75 | |
76 | /* find out if lpFileName is a partial or full path */ |
77 | if ('\\' == *lpFileName || '/' == *lpFileName) |
78 | { |
79 | fullPath = TRUE; |
80 | } |
81 | |
82 | if(fullPath) |
83 | { |
84 | if( !unixPath.Set(lpFileName, strlen(lpFileName))) |
85 | { |
86 | ERROR("Set() failed;\n" ); |
87 | SetLastError(ERROR_NOT_ENOUGH_MEMORY); |
88 | goto done; |
89 | } |
90 | } |
91 | else |
92 | { |
93 | |
94 | /* build full path */ |
95 | if(!GetCurrentDirectoryA(unixPath)) |
96 | { |
97 | /* no reason for this to fail now... */ |
98 | ASSERT("GetCurrentDirectoryA() failed! lasterror is %#xd\n" , |
99 | GetLastError()); |
100 | SetLastError(ERROR_INTERNAL_ERROR); |
101 | goto done; |
102 | } |
103 | |
104 | if (!unixPath.Append("/" , 1) || |
105 | !unixPath.Append(lpFileName,strlen(lpFileName)) |
106 | ) |
107 | { |
108 | ERROR("Append failed!\n" ); |
109 | SetLastError(ERROR_NOT_ENOUGH_MEMORY); |
110 | goto done; |
111 | } |
112 | |
113 | } |
114 | |
115 | unixPathBuf = unixPath.OpenStringBuffer(unixPath.GetCount()); |
116 | /* do conversion to Unix path */ |
117 | FILEDosToUnixPathA( unixPathBuf ); |
118 | |
119 | /* now we can canonicalize this */ |
120 | FILECanonicalizePath(unixPathBuf); |
121 | |
122 | /* at last, we can figure out how long this path is */ |
123 | nReqPathLen = strlen(unixPathBuf); |
124 | |
125 | unixPath.CloseBuffer(nReqPathLen); |
126 | nReqPathLen++; |
127 | if(nBufferLength < nReqPathLen) |
128 | { |
129 | TRACE("reporting insufficient buffer : minimum is %d, caller " |
130 | "provided %d\n" , nReqPathLen, nBufferLength); |
131 | nRet = nReqPathLen; |
132 | goto done; |
133 | } |
134 | |
135 | nRet = nReqPathLen-1; |
136 | strcpy_s(lpBuffer, nBufferLength, unixPath); |
137 | |
138 | /* locate the filename component if caller cares */ |
139 | if(lpFilePart) |
140 | { |
141 | *lpFilePart = strrchr(lpBuffer, '/'); |
142 | |
143 | if (*lpFilePart == NULL) |
144 | { |
145 | ASSERT("Not able to find '/' in the full path.\n" ); |
146 | SetLastError( ERROR_INTERNAL_ERROR ); |
147 | nRet = 0; |
148 | goto done; |
149 | } |
150 | else |
151 | { |
152 | (*lpFilePart)++; |
153 | } |
154 | } |
155 | |
156 | done: |
157 | LOGEXIT("GetFullPathNameA returns DWORD %u\n" , nRet); |
158 | PERF_EXIT(GetFullPathNameA); |
159 | return nRet; |
160 | } |
161 | |
162 | |
163 | /*++ |
164 | Function: |
165 | GetFullPathNameW |
166 | |
167 | See MSDN doc. |
168 | --*/ |
169 | DWORD |
170 | PALAPI |
171 | GetFullPathNameW( |
172 | IN LPCWSTR lpFileName, |
173 | IN DWORD nBufferLength, |
174 | OUT LPWSTR lpBuffer, |
175 | OUT LPWSTR *lpFilePart) |
176 | { |
177 | LPSTR fileNameA; |
178 | CHAR * bufferA; |
179 | size_t bufferASize = 0; |
180 | PathCharString bufferAPS; |
181 | LPSTR lpFilePartA; |
182 | int fileNameLength; |
183 | int srcSize; |
184 | DWORD length; |
185 | DWORD nRet = 0; |
186 | |
187 | PERF_ENTRY(GetFullPathNameW); |
188 | ENTRY("GetFullPathNameW(lpFileName=%p (%S), nBufferLength=%u, lpBuffer=%p" |
189 | ", lpFilePart=%p)\n" , |
190 | lpFileName?lpFileName:W16_NULLSTRING, |
191 | lpFileName?lpFileName:W16_NULLSTRING, nBufferLength, |
192 | lpBuffer, lpFilePart); |
193 | |
194 | |
195 | fileNameLength = WideCharToMultiByte(CP_ACP, 0, lpFileName, |
196 | -1, NULL, 0, NULL, NULL); |
197 | if (fileNameLength == 0) |
198 | { |
199 | /* Couldn't convert to ANSI. That's odd. */ |
200 | SetLastError(ERROR_INVALID_PARAMETER); |
201 | goto done; |
202 | } |
203 | else |
204 | { |
205 | fileNameA = static_cast<LPSTR>(alloca(fileNameLength)); |
206 | } |
207 | |
208 | /* Now convert lpFileName to ANSI. */ |
209 | srcSize = WideCharToMultiByte (CP_ACP, 0, lpFileName, |
210 | -1, fileNameA, fileNameLength, |
211 | NULL, NULL ); |
212 | if( srcSize == 0 ) |
213 | { |
214 | DWORD dwLastError = GetLastError(); |
215 | ASSERT("WideCharToMultiByte failure! error is %d\n" , dwLastError); |
216 | SetLastError(ERROR_INVALID_PARAMETER); |
217 | goto done; |
218 | } |
219 | |
220 | bufferASize = nBufferLength * MaxWCharToAcpLengthRatio; |
221 | bufferA = bufferAPS.OpenStringBuffer(bufferASize); |
222 | if (NULL == bufferA) |
223 | { |
224 | SetLastError(ERROR_NOT_ENOUGH_MEMORY); |
225 | goto done; |
226 | } |
227 | length = GetFullPathNameA(fileNameA, bufferASize, bufferA, &lpFilePartA); |
228 | bufferAPS.CloseBuffer(length); |
229 | |
230 | if (length == 0 || length > bufferASize) |
231 | { |
232 | /* Last error is set by GetFullPathNameA */ |
233 | nRet = length; |
234 | goto done; |
235 | } |
236 | |
237 | /* Convert back to Unicode the result */ |
238 | nRet = MultiByteToWideChar( CP_ACP, 0, bufferA, -1, |
239 | lpBuffer, nBufferLength ); |
240 | |
241 | if (nRet == 0) |
242 | { |
243 | if ( GetLastError() == ERROR_INSUFFICIENT_BUFFER ) |
244 | { |
245 | /* get the required length */ |
246 | nRet = MultiByteToWideChar( CP_ACP, 0, bufferA, -1, |
247 | NULL, 0 ); |
248 | SetLastError(ERROR_BUFFER_OVERFLOW); |
249 | } |
250 | |
251 | goto done; |
252 | } |
253 | |
254 | /* MultiByteToWideChar counts the trailing NULL, but |
255 | GetFullPathName does not. */ |
256 | nRet--; |
257 | |
258 | /* now set lpFilePart */ |
259 | if (lpFilePart != NULL) |
260 | { |
261 | *lpFilePart = lpBuffer; |
262 | *lpFilePart += MultiByteToWideChar( CP_ACP, 0, bufferA, |
263 | lpFilePartA - bufferA, NULL, 0); |
264 | } |
265 | |
266 | done: |
267 | LOGEXIT("GetFullPathNameW returns DWORD %u\n" , nRet); |
268 | PERF_EXIT(GetFullPathNameW); |
269 | return nRet; |
270 | } |
271 | |
272 | |
273 | /*++ |
274 | Function: |
275 | GetLongPathNameW |
276 | |
277 | See MSDN doc. |
278 | |
279 | Note: |
280 | Since short path names are not implemented (nor supported) in the PAL, |
281 | this function simply copies the given path into the new buffer. |
282 | |
283 | --*/ |
284 | DWORD |
285 | PALAPI |
286 | GetLongPathNameW( |
287 | IN LPCWSTR lpszShortPath, |
288 | OUT LPWSTR lpszLongPath, |
289 | IN DWORD cchBuffer) |
290 | { |
291 | DWORD dwPathLen = 0; |
292 | |
293 | PERF_ENTRY(GetLongPathNameW); |
294 | ENTRY("GetLongPathNameW(lpszShortPath=%p (%S), lpszLongPath=%p (%S), " |
295 | "cchBuffer=%d\n" , lpszShortPath, lpszShortPath, lpszLongPath, lpszLongPath, cchBuffer); |
296 | |
297 | if ( !lpszShortPath ) |
298 | { |
299 | ERROR( "lpszShortPath was not a valid pointer.\n" ) |
300 | SetLastError( ERROR_INVALID_PARAMETER ); |
301 | LOGEXIT("GetLongPathNameW returns DWORD 0\n" ); |
302 | PERF_EXIT(GetLongPathNameW); |
303 | return 0; |
304 | } |
305 | else if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW( lpszShortPath )) |
306 | { |
307 | // last error has been set by GetFileAttributes |
308 | ERROR( "lpszShortPath does not exist.\n" ) |
309 | LOGEXIT("GetLongPathNameW returns DWORD 0\n" ); |
310 | PERF_EXIT(GetLongPathNameW); |
311 | return 0; |
312 | } |
313 | |
314 | /* all lengths are # of TCHAR characters */ |
315 | /* "required size" includes space for the terminating null character */ |
316 | dwPathLen = PAL_wcslen(lpszShortPath)+1; |
317 | |
318 | /* lpszLongPath == 0 means caller is asking only for size */ |
319 | if ( lpszLongPath ) |
320 | { |
321 | if ( dwPathLen > cchBuffer ) |
322 | { |
323 | ERROR("Buffer is too small, need %d characters\n" , dwPathLen); |
324 | SetLastError( ERROR_INSUFFICIENT_BUFFER ); |
325 | } else |
326 | { |
327 | if ( lpszShortPath != lpszLongPath ) |
328 | { |
329 | // Note: MSDN doesn't specify the behavior of GetLongPathName API |
330 | // if the buffers are overlap. |
331 | PAL_wcsncpy( lpszLongPath, lpszShortPath, cchBuffer ); |
332 | } |
333 | |
334 | /* actual size not including terminating null is returned */ |
335 | dwPathLen--; |
336 | } |
337 | } |
338 | |
339 | LOGEXIT("GetLongPathNameW returns DWORD %u\n" , dwPathLen); |
340 | PERF_EXIT(GetLongPathNameW); |
341 | return dwPathLen; |
342 | } |
343 | |
344 | |
345 | /*++ |
346 | Function: |
347 | GetShortPathNameW |
348 | |
349 | See MSDN doc. |
350 | |
351 | Note: |
352 | Since short path names are not implemented (nor supported) in the PAL, |
353 | this function simply copies the given path into the new buffer. |
354 | |
355 | --*/ |
356 | DWORD |
357 | PALAPI |
358 | GetShortPathNameW( |
359 | IN LPCWSTR lpszLongPath, |
360 | OUT LPWSTR lpszShortPath, |
361 | IN DWORD cchBuffer) |
362 | { |
363 | DWORD dwPathLen = 0; |
364 | |
365 | PERF_ENTRY(GetShortPathNameW); |
366 | ENTRY("GetShortPathNameW(lpszLongPath=%p (%S), lpszShortPath=%p (%S), " |
367 | "cchBuffer=%d\n" , lpszLongPath, lpszLongPath, lpszShortPath, lpszShortPath, cchBuffer); |
368 | |
369 | if ( !lpszLongPath ) |
370 | { |
371 | ERROR( "lpszLongPath was not a valid pointer.\n" ) |
372 | SetLastError( ERROR_INVALID_PARAMETER ); |
373 | LOGEXIT("GetShortPathNameW returns DWORD 0\n" ); |
374 | PERF_EXIT(GetShortPathNameW); |
375 | return 0; |
376 | } |
377 | else if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW( lpszLongPath )) |
378 | { |
379 | // last error has been set by GetFileAttributes |
380 | ERROR( "lpszLongPath does not exist.\n" ) |
381 | LOGEXIT("GetShortPathNameW returns DWORD 0\n" ); |
382 | PERF_EXIT(GetShortPathNameW); |
383 | return 0; |
384 | } |
385 | |
386 | /* all lengths are # of TCHAR characters */ |
387 | /* "required size" includes space for the terminating null character */ |
388 | dwPathLen = PAL_wcslen(lpszLongPath)+1; |
389 | |
390 | /* lpszShortPath == 0 means caller is asking only for size */ |
391 | if ( lpszShortPath ) |
392 | { |
393 | if ( dwPathLen > cchBuffer ) |
394 | { |
395 | ERROR("Buffer is too small, need %d characters\n" , dwPathLen); |
396 | SetLastError( ERROR_INSUFFICIENT_BUFFER ); |
397 | } else |
398 | { |
399 | if ( lpszLongPath != lpszShortPath ) |
400 | { |
401 | // Note: MSDN doesn't specify the behavior of GetShortPathName API |
402 | // if the buffers are overlap. |
403 | PAL_wcsncpy( lpszShortPath, lpszLongPath, cchBuffer ); |
404 | } |
405 | |
406 | /* actual size not including terminating null is returned */ |
407 | dwPathLen--; |
408 | } |
409 | } |
410 | |
411 | LOGEXIT("GetShortPathNameW returns DWORD %u\n" , dwPathLen); |
412 | PERF_EXIT(GetShortPathNameW); |
413 | return dwPathLen; |
414 | } |
415 | |
416 | |
417 | /*++ |
418 | Function: |
419 | GetTempPathA |
420 | |
421 | See MSDN. |
422 | |
423 | Notes: |
424 | On Windows, the temp path is determined by the following steps: |
425 | 1. The value of the "TMP" environment variable, or if it doesn't exist, |
426 | 2. The value of the "TEMP" environment variable, or if it doesn't exist, |
427 | 3. The Windows directory. |
428 | |
429 | On Unix, we follow in spirit: |
430 | 1. The value of the "TMPDIR" environment variable, or if it doesn't exist, |
431 | 2. The /tmp directory. |
432 | This is the same approach employed by mktemp. |
433 | |
434 | --*/ |
435 | DWORD |
436 | PALAPI |
437 | GetTempPathA( |
438 | IN DWORD nBufferLength, |
439 | OUT LPSTR lpBuffer) |
440 | { |
441 | DWORD dwPathLen = 0; |
442 | |
443 | PERF_ENTRY(GetTempPathA); |
444 | ENTRY("GetTempPathA(nBufferLength=%u, lpBuffer=%p)\n" , |
445 | nBufferLength, lpBuffer); |
446 | |
447 | if ( !lpBuffer ) |
448 | { |
449 | ERROR( "lpBuffer was not a valid pointer.\n" ) |
450 | SetLastError( ERROR_INVALID_PARAMETER ); |
451 | LOGEXIT("GetTempPathA returns DWORD %u\n" , dwPathLen); |
452 | PERF_EXIT(GetTempPathA); |
453 | return 0; |
454 | } |
455 | |
456 | /* Try the TMPDIR environment variable. This is the same env var checked by mktemp. */ |
457 | dwPathLen = GetEnvironmentVariableA("TMPDIR" , lpBuffer, nBufferLength); |
458 | if (dwPathLen > 0) |
459 | { |
460 | /* The env var existed. dwPathLen will be the length without null termination |
461 | * if the entire value was successfully retrieved, or it'll be the length |
462 | * required to store the value with null termination. |
463 | */ |
464 | if (dwPathLen < nBufferLength) |
465 | { |
466 | /* The environment variable fit in the buffer. Make sure it ends with '/'. */ |
467 | if (lpBuffer[dwPathLen - 1] != '/') |
468 | { |
469 | /* If adding the slash would still fit in our provided buffer, do it. Otherwise, |
470 | * let the caller know how much space would be needed. |
471 | */ |
472 | if (dwPathLen + 2 <= nBufferLength) |
473 | { |
474 | lpBuffer[dwPathLen++] = '/'; |
475 | lpBuffer[dwPathLen] = '\0'; |
476 | } |
477 | else |
478 | { |
479 | dwPathLen += 2; |
480 | } |
481 | } |
482 | } |
483 | else /* dwPathLen >= nBufferLength */ |
484 | { |
485 | /* The value is too long for the supplied buffer. dwPathLen will now be the |
486 | * length required to hold the value, but we don't know whether that value |
487 | * is going to be '/' terminated. Since we'll need enough space for the '/', and since |
488 | * a caller would assume that the dwPathLen we return will be sufficient, |
489 | * we make sure to account for it in dwPathLen even if that means we end up saying |
490 | * one more byte of space is needed than actually is. |
491 | */ |
492 | dwPathLen++; |
493 | } |
494 | } |
495 | else /* env var not found or was empty */ |
496 | { |
497 | /* no luck, use /tmp/ or /data/local/tmp on Android */ |
498 | const char *defaultDir = TEMP_DIRECTORY_PATH; |
499 | int defaultDirLen = strlen(defaultDir); |
500 | if (defaultDirLen < nBufferLength) |
501 | { |
502 | dwPathLen = defaultDirLen; |
503 | strcpy_s(lpBuffer, nBufferLength, defaultDir); |
504 | } |
505 | else |
506 | { |
507 | /* get the required length */ |
508 | dwPathLen = defaultDirLen + 1; |
509 | } |
510 | } |
511 | |
512 | if ( dwPathLen >= nBufferLength ) |
513 | { |
514 | ERROR("Buffer is too small, need space for %d characters including null termination\n" , dwPathLen); |
515 | SetLastError( ERROR_INSUFFICIENT_BUFFER ); |
516 | } |
517 | |
518 | LOGEXIT("GetTempPathA returns DWORD %u\n" , dwPathLen); |
519 | PERF_EXIT(GetTempPathA); |
520 | return dwPathLen; |
521 | } |
522 | |
523 | /*++ |
524 | Function: |
525 | GetTempPathW |
526 | |
527 | See MSDN. |
528 | See also the comment for GetTempPathA. |
529 | --*/ |
530 | DWORD |
531 | PALAPI |
532 | GetTempPathW( |
533 | IN DWORD nBufferLength, |
534 | OUT LPWSTR lpBuffer) |
535 | { |
536 | PERF_ENTRY(GetTempPathW); |
537 | ENTRY("GetTempPathW(nBufferLength=%u, lpBuffer=%p)\n" , |
538 | nBufferLength, lpBuffer); |
539 | |
540 | if (!lpBuffer) |
541 | { |
542 | ERROR("lpBuffer was not a valid pointer.\n" ) |
543 | SetLastError(ERROR_INVALID_PARAMETER); |
544 | LOGEXIT("GetTempPathW returns DWORD 0\n" ); |
545 | PERF_EXIT(GetTempPathW); |
546 | return 0; |
547 | } |
548 | |
549 | char TempBuffer[nBufferLength > 0 ? nBufferLength : 1]; |
550 | DWORD dwRetVal = GetTempPathA( nBufferLength, TempBuffer ); |
551 | |
552 | if ( dwRetVal >= nBufferLength ) |
553 | { |
554 | ERROR( "lpBuffer was not large enough.\n" ) |
555 | SetLastError( ERROR_INSUFFICIENT_BUFFER ); |
556 | *lpBuffer = '\0'; |
557 | } |
558 | else if ( dwRetVal != 0 ) |
559 | { |
560 | /* Convert to wide. */ |
561 | if ( 0 == MultiByteToWideChar( CP_ACP, 0, TempBuffer, -1, |
562 | lpBuffer, dwRetVal + 1 ) ) |
563 | { |
564 | ASSERT( "An error occurred while converting the string to wide.\n" ); |
565 | SetLastError( ERROR_INTERNAL_ERROR ); |
566 | dwRetVal = 0; |
567 | } |
568 | } |
569 | else |
570 | { |
571 | ERROR( "The function failed.\n" ); |
572 | *lpBuffer = '\0'; |
573 | } |
574 | |
575 | LOGEXIT("GetTempPathW returns DWORD %u\n" , dwRetVal ); |
576 | PERF_EXIT(GetTempPathW); |
577 | return dwRetVal; |
578 | } |
579 | |
580 | |
581 | |
582 | /*++ |
583 | Function: |
584 | FileDosToUnixPathA |
585 | |
586 | Abstract: |
587 | Change a DOS path to a Unix path. |
588 | |
589 | Replaces '\' by '/', removes any trailing dots on directory/filenames, |
590 | and changes '*.*' to be equal to '*' |
591 | |
592 | Parameter: |
593 | IN/OUT lpPath: path to be modified |
594 | --*/ |
595 | void |
596 | FILEDosToUnixPathA( |
597 | LPSTR lpPath) |
598 | { |
599 | LPSTR p; |
600 | LPSTR pPointAtDot=NULL; |
601 | char charBeforeFirstDot='\0'; |
602 | |
603 | TRACE("Original DOS path = [%s]\n" , lpPath); |
604 | |
605 | if (!lpPath) |
606 | { |
607 | return; |
608 | } |
609 | |
610 | for (p = lpPath; *p; p++) |
611 | { |
612 | /* Make the \\ to / switch first */ |
613 | if (*p == '\\') |
614 | { |
615 | /* Replace \ with / */ |
616 | *p = '/'; |
617 | } |
618 | |
619 | if (pPointAtDot) |
620 | { |
621 | /* If pPointAtDot is not NULL, it is pointing at the first encountered |
622 | dot. If we encountered a \, that means it could be a trailing dot */ |
623 | if (*p == '/') |
624 | { |
625 | /* If char before the first dot is a '\' or '.' (special case if the |
626 | dot is the first char in the path) , then we leave it alone, |
627 | because it is either . or .., otherwise it is a trailing dot |
628 | pattern and will be truncated */ |
629 | if (charBeforeFirstDot != '.' && charBeforeFirstDot != '/') |
630 | { |
631 | memmove(pPointAtDot,p,(strlen(p)*sizeof(char))+1); |
632 | p = pPointAtDot; |
633 | } |
634 | pPointAtDot = NULL; /* Need to reset this */ |
635 | } |
636 | else if (*p == '*') |
637 | { |
638 | /* Check our size before doing anything with our pointers */ |
639 | if ((p - lpPath) >= 3) |
640 | { |
641 | /* At this point, we know that there is 1 or more dots and |
642 | then a star. AND we know the size of our string at this |
643 | point is at least 3 (so we can go backwards from our pointer |
644 | safely AND there could possilby be two characters back) |
645 | So lets check if there is a '*' and a '.' before, if there |
646 | is, replace just a '*'. Otherwise, reset pPointAtDot to NULL |
647 | and do nothing */ |
648 | if (p[-2] == '*' && |
649 | p[-1] == '.' && |
650 | p[0] == '*') |
651 | { |
652 | memmove(&(p[-2]),p,(strlen(p)*sizeof(char))+1); |
653 | } |
654 | |
655 | pPointAtDot = NULL; |
656 | } |
657 | } |
658 | else if (*p != '.') |
659 | { |
660 | /* If we are here, that means that this is NOT a trailing dot, |
661 | some other character is here, so forget our pointer */ |
662 | pPointAtDot = NULL; |
663 | } |
664 | } |
665 | else |
666 | { |
667 | if (*p == '.') |
668 | { |
669 | /* If pPointAtDot is NULL, and we encounter a dot, save the pointer */ |
670 | pPointAtDot = p; |
671 | if (pPointAtDot != lpPath) |
672 | { |
673 | charBeforeFirstDot = p[-1]; |
674 | } |
675 | else |
676 | { |
677 | charBeforeFirstDot = lpPath[0]; |
678 | } |
679 | } |
680 | } |
681 | } |
682 | |
683 | /* If pPointAtDot still points at anything, then we still have trailing dots. |
684 | Truncate at pPointAtDot, unless the dots are path specifiers (. or ..) */ |
685 | if (pPointAtDot) |
686 | { |
687 | /* make sure the trailing dots don't follow a '/', and that they aren't |
688 | the only thing in the name */ |
689 | if(pPointAtDot != lpPath && *(pPointAtDot-1) != '/') |
690 | { |
691 | *pPointAtDot = '\0'; |
692 | } |
693 | } |
694 | |
695 | TRACE("Resulting Unix path = [%s]\n" , lpPath); |
696 | } |
697 | |
698 | void |
699 | FILEDosToUnixPathA( |
700 | PathCharString& lpPath) |
701 | { |
702 | |
703 | SIZE_T len = lpPath.GetCount(); |
704 | LPSTR lpPathBuf = lpPath.OpenStringBuffer(len); |
705 | FILEDosToUnixPathA(lpPathBuf); |
706 | lpPath.CloseBuffer(len); |
707 | |
708 | } |
709 | |
710 | /*++ |
711 | Function: |
712 | FileDosToUnixPathW |
713 | |
714 | Abstract: |
715 | Change a DOS path to a Unix path. |
716 | |
717 | Replaces '\' by '/', removes any trailing dots on directory/filenames, |
718 | and changes '*.*' to be equal to '*' |
719 | |
720 | Parameter: |
721 | IN/OUT lpPath: path to be modified |
722 | --*/ |
723 | void |
724 | FILEDosToUnixPathW( |
725 | LPWSTR lpPath) |
726 | { |
727 | LPWSTR p; |
728 | LPWSTR pPointAtDot=NULL; |
729 | WCHAR charBeforeFirstDot='\0'; |
730 | |
731 | TRACE("Original DOS path = [%S]\n" , lpPath); |
732 | |
733 | if (!lpPath) |
734 | { |
735 | return; |
736 | } |
737 | |
738 | for (p = lpPath; *p; p++) |
739 | { |
740 | /* Make the \\ to / switch first */ |
741 | if (*p == '\\') |
742 | { |
743 | /* Replace \ with / */ |
744 | *p = '/'; |
745 | } |
746 | |
747 | if (pPointAtDot) |
748 | { |
749 | /* If pPointAtDot is not NULL, it is pointing at the first encountered |
750 | dot. If we encountered a \, that means it could be a trailing dot */ |
751 | if (*p == '/') |
752 | { |
753 | /* If char before the first dot is a '\' or '.' (special case if the |
754 | dot is the first char in the path) , then we leave it alone, |
755 | because it is either . or .., otherwise it is a trailing dot |
756 | pattern and will be truncated */ |
757 | if (charBeforeFirstDot != '.' && charBeforeFirstDot != '/') |
758 | { |
759 | memmove(pPointAtDot,p,((PAL_wcslen(p)+1)*sizeof(WCHAR))); |
760 | p = pPointAtDot; |
761 | } |
762 | pPointAtDot = NULL; /* Need to reset this */ |
763 | } |
764 | else if (*p == '*') |
765 | { |
766 | /* Check our size before doing anything with our pointers */ |
767 | if ((p - lpPath) >= 3) |
768 | { |
769 | /* At this point, we know that there is 1 or more dots and |
770 | then a star. AND we know the size of our string at this |
771 | point is at least 3 (so we can go backwards from our pointer |
772 | safely AND there could possilby be two characters back) |
773 | So lets check if there is a '*' and a '.' before, if there |
774 | is, replace just a '*'. Otherwise, reset pPointAtDot to NULL |
775 | and do nothing */ |
776 | if (p[-2] == '*' && |
777 | p[-1] == '.' && |
778 | p[0] == '*') |
779 | { |
780 | memmove(&(p[-2]),p,(PAL_wcslen(p)*sizeof(WCHAR))); |
781 | } |
782 | |
783 | pPointAtDot = NULL; |
784 | } |
785 | } |
786 | else if (*p != '.') |
787 | { |
788 | /* If we are here, that means that this is NOT a trailing dot, |
789 | some other character is here, so forget our pointer */ |
790 | pPointAtDot = NULL; |
791 | } |
792 | } |
793 | else |
794 | { |
795 | if (*p == '.') |
796 | { |
797 | /* If pPointAtDot is NULL, and we encounter a dot, save the pointer */ |
798 | pPointAtDot = p; |
799 | if (pPointAtDot != lpPath) |
800 | { |
801 | charBeforeFirstDot = p[-1]; |
802 | } |
803 | else |
804 | { |
805 | charBeforeFirstDot = lpPath[0]; |
806 | } |
807 | } |
808 | } |
809 | } |
810 | |
811 | /* If pPointAtDot still points at anything, then we still have trailing dots. |
812 | Truncate at pPointAtDot, unless the dots are path specifiers (. or ..) */ |
813 | if (pPointAtDot) |
814 | { |
815 | /* make sure the trailing dots don't follow a '/', and that they aren't |
816 | the only thing in the name */ |
817 | if(pPointAtDot != lpPath && *(pPointAtDot-1) != '/') |
818 | { |
819 | *pPointAtDot = '\0'; |
820 | } |
821 | } |
822 | |
823 | TRACE("Resulting Unix path = [%S]\n" , lpPath); |
824 | } |
825 | |
826 | |
827 | /*++ |
828 | Function: |
829 | FileUnixToDosPathA |
830 | |
831 | Abstract: |
832 | Change a Unix path to a DOS path. Replace '/' by '\'. |
833 | |
834 | Parameter: |
835 | IN/OUT lpPath: path to be modified |
836 | --*/ |
837 | void |
838 | FILEUnixToDosPathA( |
839 | LPSTR lpPath) |
840 | { |
841 | LPSTR p; |
842 | |
843 | TRACE("Original Unix path = [%s]\n" , lpPath); |
844 | |
845 | if (!lpPath) |
846 | return; |
847 | |
848 | for (p = lpPath; *p; p++) |
849 | { |
850 | if (*p == '/') |
851 | *p = '\\'; |
852 | } |
853 | |
854 | TRACE("Resulting DOS path = [%s]\n" , lpPath); |
855 | } |
856 | |
857 | |
858 | /*++ |
859 | Function: |
860 | FILEGetDirectoryFromFullPathA |
861 | |
862 | Parse the given path. If it contains a directory part and a file part, |
863 | put the directory part into the supplied buffer, and return the number of |
864 | characters written to the buffer. If the buffer is not large enough, |
865 | return the required size of the buffer including the NULL character. If |
866 | there is no directory part in the path, return 0. |
867 | --*/ |
868 | DWORD FILEGetDirectoryFromFullPathA( LPCSTR lpFullPath, |
869 | DWORD nBufferLength, |
870 | LPSTR lpBuffer ) |
871 | { |
872 | size_t full_len, dir_len, i; |
873 | LPCSTR lpDirEnd; |
874 | DWORD dwRetLength; |
875 | |
876 | full_len = strlen( lpFullPath ); |
877 | |
878 | /* look for the first path separator backwards */ |
879 | lpDirEnd = lpFullPath + full_len - 1; |
880 | while( lpDirEnd >= lpFullPath && *lpDirEnd != '/' && *lpDirEnd != '\\') |
881 | --lpDirEnd; |
882 | |
883 | dir_len = lpDirEnd - lpFullPath + 1; /* +1 for fencepost */ |
884 | |
885 | if ( dir_len <= 0 ) |
886 | { |
887 | dwRetLength = 0; |
888 | } |
889 | else if (dir_len >= nBufferLength) |
890 | { |
891 | dwRetLength = dir_len + 1; /* +1 for NULL char */ |
892 | } |
893 | else |
894 | { |
895 | /* put the directory into the buffer, including 1 or more |
896 | trailing path separators */ |
897 | for( i = 0; i < dir_len; ++i ) |
898 | *(lpBuffer + i) = *(lpFullPath + i); |
899 | |
900 | *(lpBuffer + i) = '\0'; |
901 | |
902 | dwRetLength = dir_len; |
903 | } |
904 | |
905 | return( dwRetLength ); |
906 | } |
907 | |
908 | /*++ |
909 | Function: |
910 | FILEGetFileNameFromFullPath |
911 | |
912 | Given a full path, return a pointer to the first char of the filename part. |
913 | --*/ |
914 | LPCSTR FILEGetFileNameFromFullPathA( LPCSTR lpFullPath ) |
915 | { |
916 | int DirLen = FILEGetDirectoryFromFullPathA( lpFullPath, 0, NULL ); |
917 | |
918 | if ( DirLen > 0 ) |
919 | { |
920 | return lpFullPath + DirLen - 1; |
921 | } |
922 | else |
923 | { |
924 | return lpFullPath; |
925 | } |
926 | } |
927 | |
928 | /*++ |
929 | FILECanonicalizePath |
930 | Removes all instances of '/./', '/../' and '//' from an absolute path. |
931 | |
932 | Parameters: |
933 | LPSTR lpUnixPath : absolute path to modify, in Unix format |
934 | |
935 | (no return value) |
936 | |
937 | Notes : |
938 | -behavior is undefined if path is not absolute |
939 | -the order of steps *is* important: /one/./../two would give /one/two |
940 | instead of /two if step 3 was done before step 2 |
941 | -reason for this function is that GetFullPathName can't use realpath(), since |
942 | realpath() requires the given path to be valid and GetFullPathName does not. |
943 | --*/ |
944 | void FILECanonicalizePath(LPSTR lpUnixPath) |
945 | { |
946 | LPSTR slashslashptr; |
947 | LPSTR dotdotptr; |
948 | LPSTR slashdotptr; |
949 | LPSTR slashptr; |
950 | |
951 | /* step 1 : replace '//' sequences by a single '/' */ |
952 | |
953 | slashslashptr = lpUnixPath; |
954 | while(1) |
955 | { |
956 | slashslashptr = strstr(slashslashptr,"//" ); |
957 | if(NULL == slashslashptr) |
958 | { |
959 | break; |
960 | } |
961 | /* remove extra '/' */ |
962 | TRACE("stripping '//' from %s\n" , lpUnixPath); |
963 | memmove(slashslashptr,slashslashptr+1,strlen(slashslashptr+1)+1); |
964 | } |
965 | |
966 | /* step 2 : replace '/./' sequences by a single '/' */ |
967 | |
968 | slashdotptr = lpUnixPath; |
969 | while(1) |
970 | { |
971 | slashdotptr = strstr(slashdotptr,"/./" ); |
972 | if(NULL == slashdotptr) |
973 | { |
974 | break; |
975 | } |
976 | /* strip the extra '/.' */ |
977 | TRACE("removing '/./' sequence from %s\n" , lpUnixPath); |
978 | memmove(slashdotptr,slashdotptr+2,strlen(slashdotptr+2)+1); |
979 | } |
980 | |
981 | /* step 3 : replace '/<name>/../' sequences by a single '/' */ |
982 | |
983 | while(1) |
984 | { |
985 | dotdotptr = strstr(lpUnixPath,"/../" ); |
986 | if(NULL == dotdotptr) |
987 | { |
988 | break; |
989 | } |
990 | if(dotdotptr == lpUnixPath) |
991 | { |
992 | /* special case : '/../' at the beginning of the path are replaced |
993 | by a single '/' */ |
994 | TRACE("stripping leading '/../' from %s\n" , lpUnixPath); |
995 | memmove(lpUnixPath, lpUnixPath+3,strlen(lpUnixPath+3)+1); |
996 | continue; |
997 | } |
998 | |
999 | /* null-terminate the string before the '/../', so that strrchr will |
1000 | start looking right before it */ |
1001 | *dotdotptr = '\0'; |
1002 | slashptr = strrchr(lpUnixPath,'/'); |
1003 | if(NULL == slashptr) |
1004 | { |
1005 | /* this happens if this function was called with a relative path. |
1006 | don't do that. */ |
1007 | ASSERT("can't find leading '/' before '/../ sequence\n" ); |
1008 | break; |
1009 | } |
1010 | TRACE("removing '/<dir>/../' sequence from %s\n" , lpUnixPath); |
1011 | memmove(slashptr,dotdotptr+3,strlen(dotdotptr+3)+1); |
1012 | } |
1013 | |
1014 | /* step 4 : remove a trailing '/..' */ |
1015 | |
1016 | dotdotptr = strstr(lpUnixPath,"/.." ); |
1017 | if(dotdotptr == lpUnixPath) |
1018 | { |
1019 | /* if the full path is simply '/..', replace it by '/' */ |
1020 | lpUnixPath[1] = '\0'; |
1021 | } |
1022 | else if(NULL != dotdotptr && '\0' == dotdotptr[3]) |
1023 | { |
1024 | *dotdotptr = '\0'; |
1025 | slashptr = strrchr(lpUnixPath,'/'); |
1026 | if(NULL != slashptr) |
1027 | { |
1028 | /* make sure the last slash isn't the root */ |
1029 | if (slashptr == lpUnixPath) |
1030 | { |
1031 | lpUnixPath[1] = '\0'; |
1032 | } |
1033 | else |
1034 | { |
1035 | *slashptr = '\0'; |
1036 | } |
1037 | } |
1038 | } |
1039 | |
1040 | /* step 5 : remove a traling '/.' */ |
1041 | |
1042 | slashdotptr = strstr(lpUnixPath,"/." ); |
1043 | if (slashdotptr != NULL && slashdotptr[2] == '\0') |
1044 | { |
1045 | if(slashdotptr == lpUnixPath) |
1046 | { |
1047 | // if the full path is simply '/.', replace it by '/' */ |
1048 | lpUnixPath[1] = '\0'; |
1049 | } |
1050 | else |
1051 | { |
1052 | *slashdotptr = '\0'; |
1053 | } |
1054 | } |
1055 | } |
1056 | |
1057 | |
1058 | /*++ |
1059 | Function: |
1060 | SearchPathA |
1061 | |
1062 | See MSDN doc. |
1063 | |
1064 | PAL-specific notes : |
1065 | -lpPath must be non-NULL; path delimiters are platform-dependent (':' for Unix) |
1066 | -lpFileName must be non-NULL, may be an absolute path |
1067 | -lpExtension must be NULL |
1068 | -lpFilePart (if non-NULL) doesn't need to be used (but we do) |
1069 | --*/ |
1070 | DWORD |
1071 | PALAPI |
1072 | SearchPathA( |
1073 | IN LPCSTR lpPath, |
1074 | IN LPCSTR lpFileName, |
1075 | IN LPCSTR lpExtension, |
1076 | IN DWORD nBufferLength, |
1077 | OUT LPSTR lpBuffer, |
1078 | OUT LPSTR *lpFilePart |
1079 | ) |
1080 | { |
1081 | DWORD nRet = 0; |
1082 | CHAR * FullPath; |
1083 | size_t FullPathLength = 0; |
1084 | PathCharString FullPathPS; |
1085 | PathCharString CanonicalFullPathPS; |
1086 | CHAR * CanonicalFullPath; |
1087 | LPCSTR pPathStart; |
1088 | LPCSTR pPathEnd; |
1089 | size_t PathLength; |
1090 | size_t FileNameLength; |
1091 | DWORD length; |
1092 | DWORD dw; |
1093 | |
1094 | PERF_ENTRY(SearchPathA); |
1095 | ENTRY("SearchPathA(lpPath=%p (%s), lpFileName=%p (%s), lpExtension=%p, " |
1096 | "nBufferLength=%u, lpBuffer=%p, lpFilePart=%p)\n" , |
1097 | lpPath, |
1098 | lpPath, lpFileName, lpFileName, lpExtension, nBufferLength, lpBuffer, |
1099 | lpFilePart); |
1100 | |
1101 | /* validate parameters */ |
1102 | |
1103 | if(NULL == lpPath) |
1104 | { |
1105 | ASSERT("lpPath may not be NULL\n" ); |
1106 | SetLastError(ERROR_INVALID_PARAMETER); |
1107 | goto done; |
1108 | } |
1109 | if(NULL == lpFileName) |
1110 | { |
1111 | ASSERT("lpFileName may not be NULL\n" ); |
1112 | SetLastError(ERROR_INVALID_PARAMETER); |
1113 | goto done; |
1114 | } |
1115 | if(NULL != lpExtension) |
1116 | { |
1117 | ASSERT("lpExtension must be NULL, is %p instead\n" , lpExtension); |
1118 | SetLastError(ERROR_INVALID_PARAMETER); |
1119 | goto done; |
1120 | } |
1121 | |
1122 | FileNameLength = strlen(lpFileName); |
1123 | |
1124 | /* special case : if file name contains absolute path, don't search the |
1125 | provided path */ |
1126 | if('\\' == lpFileName[0] || '/' == lpFileName[0]) |
1127 | { |
1128 | /* Canonicalize the path to deal with back-to-back '/', etc. */ |
1129 | length = FileNameLength; |
1130 | CanonicalFullPath = CanonicalFullPathPS.OpenStringBuffer(length); |
1131 | if (NULL == CanonicalFullPath) |
1132 | { |
1133 | SetLastError(ERROR_NOT_ENOUGH_MEMORY); |
1134 | goto done; |
1135 | } |
1136 | dw = GetFullPathNameA(lpFileName, length+1, CanonicalFullPath, NULL); |
1137 | CanonicalFullPathPS.CloseBuffer(dw); |
1138 | |
1139 | if (length+1 < dw) |
1140 | { |
1141 | CanonicalFullPath = CanonicalFullPathPS.OpenStringBuffer(dw-1); |
1142 | if (NULL == CanonicalFullPath) |
1143 | { |
1144 | SetLastError(ERROR_NOT_ENOUGH_MEMORY); |
1145 | goto done; |
1146 | } |
1147 | dw = GetFullPathNameA(lpFileName, dw, |
1148 | CanonicalFullPath, NULL); |
1149 | CanonicalFullPathPS.CloseBuffer(dw); |
1150 | } |
1151 | |
1152 | if (dw == 0) |
1153 | { |
1154 | WARN("couldn't canonicalize path <%s>, error is %#x. failing.\n" , |
1155 | lpFileName, GetLastError()); |
1156 | SetLastError(ERROR_INVALID_PARAMETER); |
1157 | goto done; |
1158 | } |
1159 | |
1160 | /* see if the file exists */ |
1161 | if(0 == access(CanonicalFullPath, F_OK)) |
1162 | { |
1163 | /* found it */ |
1164 | nRet = dw; |
1165 | } |
1166 | } |
1167 | else |
1168 | { |
1169 | LPCSTR pNextPath; |
1170 | |
1171 | pNextPath = lpPath; |
1172 | |
1173 | while (*pNextPath) |
1174 | { |
1175 | pPathStart = pNextPath; |
1176 | |
1177 | /* get a pointer to the end of the first path in pPathStart */ |
1178 | pPathEnd = strchr(pPathStart, ':'); |
1179 | if (!pPathEnd) |
1180 | { |
1181 | pPathEnd = pPathStart + strlen(pPathStart); |
1182 | /* we want to break out of the loop after this pass, so let |
1183 | *pNextPath be '\0' */ |
1184 | pNextPath = pPathEnd; |
1185 | } |
1186 | else |
1187 | { |
1188 | /* point to the next component in the path string */ |
1189 | pNextPath = pPathEnd+1; |
1190 | } |
1191 | |
1192 | PathLength = pPathEnd-pPathStart; |
1193 | |
1194 | if(0 == PathLength) |
1195 | { |
1196 | /* empty component : there were 2 consecutive ':' */ |
1197 | continue; |
1198 | } |
1199 | |
1200 | /* Construct a pathname by concatenating one path from lpPath, '/' |
1201 | and lpFileName */ |
1202 | FullPathLength = PathLength + FileNameLength; |
1203 | FullPath = FullPathPS.OpenStringBuffer(FullPathLength+1); |
1204 | if (NULL == FullPath) |
1205 | { |
1206 | SetLastError(ERROR_NOT_ENOUGH_MEMORY); |
1207 | goto done; |
1208 | } |
1209 | memcpy(FullPath, pPathStart, PathLength); |
1210 | FullPath[PathLength] = '/'; |
1211 | if (strcpy_s(&FullPath[PathLength+1], FullPathLength+1-PathLength, lpFileName) != SAFECRT_SUCCESS) |
1212 | { |
1213 | ERROR("strcpy_s failed!\n" ); |
1214 | SetLastError( ERROR_FILENAME_EXCED_RANGE ); |
1215 | nRet = 0; |
1216 | goto done; |
1217 | } |
1218 | |
1219 | FullPathPS.CloseBuffer(FullPathLength+1); |
1220 | /* Canonicalize the path to deal with back-to-back '/', etc. */ |
1221 | length = MAX_LONGPATH; //Use it for first try |
1222 | CanonicalFullPath = CanonicalFullPathPS.OpenStringBuffer(length); |
1223 | if (NULL == CanonicalFullPath) |
1224 | { |
1225 | SetLastError(ERROR_NOT_ENOUGH_MEMORY); |
1226 | goto done; |
1227 | } |
1228 | dw = GetFullPathNameA(FullPath, length+1, |
1229 | CanonicalFullPath, NULL); |
1230 | CanonicalFullPathPS.CloseBuffer(dw); |
1231 | |
1232 | if (length+1 < dw) |
1233 | { |
1234 | CanonicalFullPath = CanonicalFullPathPS.OpenStringBuffer(dw-1); |
1235 | dw = GetFullPathNameA(FullPath, dw, |
1236 | CanonicalFullPath, NULL); |
1237 | CanonicalFullPathPS.CloseBuffer(dw); |
1238 | } |
1239 | |
1240 | if (dw == 0) |
1241 | { |
1242 | /* Call failed - possibly low memory. Skip the path */ |
1243 | WARN("couldn't canonicalize path <%s>, error is %#x. " |
1244 | "skipping it\n" , FullPath, GetLastError()); |
1245 | continue; |
1246 | } |
1247 | |
1248 | /* see if the file exists */ |
1249 | if(0 == access(CanonicalFullPath, F_OK)) |
1250 | { |
1251 | /* found it */ |
1252 | nRet = dw; |
1253 | break; |
1254 | } |
1255 | } |
1256 | } |
1257 | |
1258 | if (nRet == 0) |
1259 | { |
1260 | /* file not found anywhere; say so. in Windows, this always seems to say |
1261 | FILE_NOT_FOUND, even if path doesn't exist */ |
1262 | SetLastError(ERROR_FILE_NOT_FOUND); |
1263 | } |
1264 | else |
1265 | { |
1266 | if (nRet < nBufferLength) |
1267 | { |
1268 | if(NULL == lpBuffer) |
1269 | { |
1270 | /* Windows merily crashes here, but let's not */ |
1271 | ERROR("caller told us buffer size was %d, but buffer is NULL\n" , |
1272 | nBufferLength); |
1273 | SetLastError(ERROR_INVALID_PARAMETER); |
1274 | nRet = 0; |
1275 | goto done; |
1276 | } |
1277 | |
1278 | if (strcpy_s(lpBuffer, nBufferLength, CanonicalFullPath) != SAFECRT_SUCCESS) |
1279 | { |
1280 | ERROR("strcpy_s failed!\n" ); |
1281 | SetLastError( ERROR_FILENAME_EXCED_RANGE ); |
1282 | nRet = 0; |
1283 | goto done; |
1284 | } |
1285 | |
1286 | if(NULL != lpFilePart) |
1287 | { |
1288 | *lpFilePart = strrchr(lpBuffer,'/'); |
1289 | if(NULL == *lpFilePart) |
1290 | { |
1291 | ASSERT("no '/' in full path!\n" ); |
1292 | } |
1293 | else |
1294 | { |
1295 | /* point to character after last '/' */ |
1296 | (*lpFilePart)++; |
1297 | } |
1298 | } |
1299 | } |
1300 | else |
1301 | { |
1302 | /* if buffer is too small, report required length, including |
1303 | terminating null */ |
1304 | nRet++; |
1305 | } |
1306 | } |
1307 | done: |
1308 | LOGEXIT("SearchPathA returns DWORD %u\n" , nRet); |
1309 | PERF_EXIT(SearchPathA); |
1310 | return nRet; |
1311 | } |
1312 | |
1313 | |
1314 | /*++ |
1315 | Function: |
1316 | SearchPathW |
1317 | |
1318 | See MSDN doc. |
1319 | |
1320 | PAL-specific notes : |
1321 | -lpPath must be non-NULL; path delimiters are platform-dependent (':' for Unix) |
1322 | -lpFileName must be non-NULL, may be an absolute path |
1323 | -lpExtension must be NULL |
1324 | -lpFilePart (if non-NULL) doesn't need to be used (but we do) |
1325 | --*/ |
1326 | DWORD |
1327 | PALAPI |
1328 | SearchPathW( |
1329 | IN LPCWSTR lpPath, |
1330 | IN LPCWSTR lpFileName, |
1331 | IN LPCWSTR lpExtension, |
1332 | IN DWORD nBufferLength, |
1333 | OUT LPWSTR lpBuffer, |
1334 | OUT LPWSTR *lpFilePart |
1335 | ) |
1336 | { |
1337 | DWORD nRet = 0; |
1338 | WCHAR * FullPath; |
1339 | size_t FullPathLength = 0; |
1340 | PathWCharString FullPathPS; |
1341 | LPCWSTR pPathStart; |
1342 | LPCWSTR pPathEnd; |
1343 | size_t PathLength; |
1344 | size_t FileNameLength; |
1345 | DWORD dw; |
1346 | DWORD length; |
1347 | char * AnsiPath; |
1348 | PathCharString AnsiPathPS; |
1349 | size_t CanonicalPathLength; |
1350 | int canonical_size; |
1351 | WCHAR * CanonicalPath; |
1352 | PathWCharString CanonicalPathPS; |
1353 | |
1354 | PERF_ENTRY(SearchPathW); |
1355 | ENTRY("SearchPathW(lpPath=%p (%S), lpFileName=%p (%S), lpExtension=%p, " |
1356 | "nBufferLength=%u, lpBuffer=%p, lpFilePart=%p)\n" , |
1357 | lpPath, |
1358 | lpPath, lpFileName, lpFileName, lpExtension, nBufferLength, lpBuffer, |
1359 | lpFilePart); |
1360 | |
1361 | /* validate parameters */ |
1362 | |
1363 | if(NULL == lpPath) |
1364 | { |
1365 | ASSERT("lpPath may not be NULL\n" ); |
1366 | SetLastError(ERROR_INVALID_PARAMETER); |
1367 | goto done; |
1368 | } |
1369 | if(NULL == lpFileName) |
1370 | { |
1371 | ASSERT("lpFileName may not be NULL\n" ); |
1372 | SetLastError(ERROR_INVALID_PARAMETER); |
1373 | goto done; |
1374 | } |
1375 | if(NULL != lpExtension) |
1376 | { |
1377 | ASSERT("lpExtension must be NULL, is %p instead\n" , lpExtension); |
1378 | SetLastError(ERROR_INVALID_PARAMETER); |
1379 | goto done; |
1380 | } |
1381 | |
1382 | /* special case : if file name contains absolute path, don't search the |
1383 | provided path */ |
1384 | if('\\' == lpFileName[0] || '/' == lpFileName[0]) |
1385 | { |
1386 | /* Canonicalize the path to deal with back-to-back '/', etc. */ |
1387 | length = MAX_LONGPATH; //Use it for first try |
1388 | CanonicalPath = CanonicalPathPS.OpenStringBuffer(length); |
1389 | if (NULL == CanonicalPath) |
1390 | { |
1391 | SetLastError(ERROR_NOT_ENOUGH_MEMORY); |
1392 | goto done; |
1393 | } |
1394 | dw = GetFullPathNameW(lpFileName, length+1, CanonicalPath, NULL); |
1395 | CanonicalPathPS.CloseBuffer(dw); |
1396 | if (length+1 < dw) |
1397 | { |
1398 | CanonicalPath = CanonicalPathPS.OpenStringBuffer(dw-1); |
1399 | if (NULL == CanonicalPath) |
1400 | { |
1401 | SetLastError(ERROR_NOT_ENOUGH_MEMORY); |
1402 | goto done; |
1403 | } |
1404 | dw = GetFullPathNameW(lpFileName, dw, CanonicalPath, NULL); |
1405 | CanonicalPathPS.CloseBuffer(dw); |
1406 | } |
1407 | |
1408 | if (dw == 0) |
1409 | { |
1410 | WARN("couldn't canonicalize path <%S>, error is %#x. failing.\n" , |
1411 | lpPath, GetLastError()); |
1412 | SetLastError(ERROR_INVALID_PARAMETER); |
1413 | goto done; |
1414 | } |
1415 | |
1416 | /* see if the file exists */ |
1417 | CanonicalPathLength = (PAL_wcslen(CanonicalPath)+1) * MaxWCharToAcpLengthRatio; |
1418 | AnsiPath = AnsiPathPS.OpenStringBuffer(CanonicalPathLength); |
1419 | if (NULL == AnsiPath) |
1420 | { |
1421 | SetLastError(ERROR_NOT_ENOUGH_MEMORY); |
1422 | goto done; |
1423 | } |
1424 | canonical_size = WideCharToMultiByte(CP_ACP, 0, CanonicalPath, -1, |
1425 | AnsiPath, CanonicalPathLength, NULL, NULL); |
1426 | AnsiPathPS.CloseBuffer(canonical_size); |
1427 | |
1428 | if(0 == access(AnsiPath, F_OK)) |
1429 | { |
1430 | /* found it */ |
1431 | nRet = dw; |
1432 | } |
1433 | } |
1434 | else |
1435 | { |
1436 | LPCWSTR pNextPath; |
1437 | |
1438 | pNextPath = lpPath; |
1439 | |
1440 | FileNameLength = PAL_wcslen(lpFileName); |
1441 | |
1442 | while (*pNextPath) |
1443 | { |
1444 | pPathStart = pNextPath; |
1445 | |
1446 | /* get a pointer to the end of the first path in pPathStart */ |
1447 | pPathEnd = PAL_wcschr(pPathStart, ':'); |
1448 | if (!pPathEnd) |
1449 | { |
1450 | pPathEnd = pPathStart + PAL_wcslen(pPathStart); |
1451 | /* we want to break out of the loop after this pass, so let |
1452 | *pNextPath be '\0' */ |
1453 | pNextPath = pPathEnd; |
1454 | } |
1455 | else |
1456 | { |
1457 | /* point to the next component in the path string */ |
1458 | pNextPath = pPathEnd+1; |
1459 | } |
1460 | |
1461 | PathLength = pPathEnd-pPathStart; |
1462 | |
1463 | if(0 == PathLength) |
1464 | { |
1465 | /* empty component : there were 2 consecutive ':' */ |
1466 | continue; |
1467 | } |
1468 | |
1469 | /* Construct a pathname by concatenating one path from lpPath, '/' |
1470 | and lpFileName */ |
1471 | FullPathLength = PathLength + FileNameLength; |
1472 | FullPath = FullPathPS.OpenStringBuffer(FullPathLength+1); |
1473 | if (NULL == FullPath) |
1474 | { |
1475 | SetLastError(ERROR_NOT_ENOUGH_MEMORY); |
1476 | goto done; |
1477 | } |
1478 | memcpy(FullPath, pPathStart, PathLength*sizeof(WCHAR)); |
1479 | FullPath[PathLength] = '/'; |
1480 | PAL_wcscpy(&FullPath[PathLength+1], lpFileName); |
1481 | |
1482 | FullPathPS.CloseBuffer(FullPathLength+1); |
1483 | |
1484 | /* Canonicalize the path to deal with back-to-back '/', etc. */ |
1485 | length = MAX_LONGPATH; //Use it for first try |
1486 | CanonicalPath = CanonicalPathPS.OpenStringBuffer(length); |
1487 | if (NULL == CanonicalPath) |
1488 | { |
1489 | SetLastError(ERROR_NOT_ENOUGH_MEMORY); |
1490 | goto done; |
1491 | } |
1492 | dw = GetFullPathNameW(FullPath, length+1, |
1493 | CanonicalPath, NULL); |
1494 | CanonicalPathPS.CloseBuffer(dw); |
1495 | |
1496 | if (length+1 < dw) |
1497 | { |
1498 | CanonicalPath = CanonicalPathPS.OpenStringBuffer(dw-1); |
1499 | if (NULL == CanonicalPath) |
1500 | { |
1501 | SetLastError(ERROR_NOT_ENOUGH_MEMORY); |
1502 | goto done; |
1503 | } |
1504 | dw = GetFullPathNameW(FullPath, dw, CanonicalPath, NULL); |
1505 | CanonicalPathPS.CloseBuffer(dw); |
1506 | } |
1507 | |
1508 | if (dw == 0) |
1509 | { |
1510 | /* Call failed - possibly low memory. Skip the path */ |
1511 | WARN("couldn't canonicalize path <%S>, error is %#x. " |
1512 | "skipping it\n" , FullPath, GetLastError()); |
1513 | continue; |
1514 | } |
1515 | |
1516 | /* see if the file exists */ |
1517 | CanonicalPathLength = (PAL_wcslen(CanonicalPath)+1) * MaxWCharToAcpLengthRatio; |
1518 | AnsiPath = AnsiPathPS.OpenStringBuffer(CanonicalPathLength); |
1519 | if (NULL == AnsiPath) |
1520 | { |
1521 | SetLastError(ERROR_NOT_ENOUGH_MEMORY); |
1522 | goto done; |
1523 | } |
1524 | canonical_size = WideCharToMultiByte(CP_ACP, 0, CanonicalPath, -1, |
1525 | AnsiPath, CanonicalPathLength, NULL, NULL); |
1526 | AnsiPathPS.CloseBuffer(canonical_size); |
1527 | |
1528 | if(0 == access(AnsiPath, F_OK)) |
1529 | { |
1530 | /* found it */ |
1531 | nRet = dw; |
1532 | break; |
1533 | } |
1534 | } |
1535 | } |
1536 | |
1537 | if (nRet == 0) |
1538 | { |
1539 | /* file not found anywhere; say so. in Windows, this always seems to say |
1540 | FILE_NOT_FOUND, even if path doesn't exist */ |
1541 | SetLastError(ERROR_FILE_NOT_FOUND); |
1542 | } |
1543 | else |
1544 | { |
1545 | /* find out the required buffer size, copy path to buffer if it's |
1546 | large enough */ |
1547 | nRet = PAL_wcslen(CanonicalPath)+1; |
1548 | if(nRet <= nBufferLength) |
1549 | { |
1550 | if(NULL == lpBuffer) |
1551 | { |
1552 | /* Windows merily crashes here, but let's not */ |
1553 | ERROR("caller told us buffer size was %d, but buffer is NULL\n" , |
1554 | nBufferLength); |
1555 | SetLastError(ERROR_INVALID_PARAMETER); |
1556 | nRet = 0; |
1557 | goto done; |
1558 | } |
1559 | PAL_wcscpy(lpBuffer, CanonicalPath); |
1560 | |
1561 | /* don't include the null-terminator in the count if buffer was |
1562 | large enough */ |
1563 | nRet--; |
1564 | |
1565 | if(NULL != lpFilePart) |
1566 | { |
1567 | *lpFilePart = PAL_wcsrchr(lpBuffer, '/'); |
1568 | if(NULL == *lpFilePart) |
1569 | { |
1570 | ASSERT("no '/' in full path!\n" ); |
1571 | } |
1572 | else |
1573 | { |
1574 | /* point to character after last '/' */ |
1575 | (*lpFilePart)++; |
1576 | } |
1577 | } |
1578 | } |
1579 | } |
1580 | done: |
1581 | LOGEXIT("SearchPathW returns DWORD %u\n" , nRet); |
1582 | PERF_EXIT(SearchPathW); |
1583 | return nRet; |
1584 | } |
1585 | |
1586 | /*++ |
1587 | Function: |
1588 | PathFindFileNameW |
1589 | |
1590 | See MSDN doc. |
1591 | --*/ |
1592 | LPWSTR |
1593 | PALAPI |
1594 | PathFindFileNameW( |
1595 | IN LPCWSTR pPath |
1596 | ) |
1597 | { |
1598 | PERF_ENTRY(PathFindFileNameW); |
1599 | ENTRY("PathFindFileNameW(pPath=%p (%S))\n" , |
1600 | pPath?pPath:W16_NULLSTRING, |
1601 | pPath?pPath:W16_NULLSTRING); |
1602 | |
1603 | LPWSTR ret = (LPWSTR)pPath; |
1604 | if (ret != NULL && *ret != W('\0')) |
1605 | { |
1606 | ret = PAL_wcschr(ret, W('\0')) - 1; |
1607 | if (ret > pPath && *ret == W('/')) |
1608 | { |
1609 | ret--; |
1610 | } |
1611 | while (ret > pPath && *ret != W('/')) |
1612 | { |
1613 | ret--; |
1614 | } |
1615 | if (*ret == W('/') && *(ret + 1) != W('\0')) |
1616 | { |
1617 | ret++; |
1618 | } |
1619 | } |
1620 | |
1621 | LOGEXIT("PathFindFileNameW returns %S\n" , ret); |
1622 | PERF_EXIT(PathFindFileNameW); |
1623 | return ret; |
1624 | } |
1625 | |