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
9Module Name:
10
11 path.c
12
13Abstract:
14
15 Implementation of all functions related to path support
16
17Revision 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
35SET_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
43int MaxWCharToAcpLengthRatio = 3;
44/*++
45Function:
46 GetFullPathNameA
47
48See MSDN doc.
49--*/
50DWORD
51PALAPI
52GetFullPathNameA(
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
156done:
157 LOGEXIT("GetFullPathNameA returns DWORD %u\n", nRet);
158 PERF_EXIT(GetFullPathNameA);
159 return nRet;
160}
161
162
163/*++
164Function:
165 GetFullPathNameW
166
167See MSDN doc.
168--*/
169DWORD
170PALAPI
171GetFullPathNameW(
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
266done:
267 LOGEXIT("GetFullPathNameW returns DWORD %u\n", nRet);
268 PERF_EXIT(GetFullPathNameW);
269 return nRet;
270}
271
272
273/*++
274Function:
275 GetLongPathNameW
276
277See MSDN doc.
278
279Note:
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--*/
284DWORD
285PALAPI
286GetLongPathNameW(
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/*++
346Function:
347 GetShortPathNameW
348
349See MSDN doc.
350
351Note:
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--*/
356DWORD
357PALAPI
358GetShortPathNameW(
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/*++
418Function:
419 GetTempPathA
420
421See MSDN.
422
423Notes:
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--*/
435DWORD
436PALAPI
437GetTempPathA(
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/*++
524Function:
525 GetTempPathW
526
527See MSDN.
528See also the comment for GetTempPathA.
529--*/
530DWORD
531PALAPI
532GetTempPathW(
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/*++
583Function:
584 FileDosToUnixPathA
585
586Abstract:
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
592Parameter:
593 IN/OUT lpPath: path to be modified
594--*/
595void
596FILEDosToUnixPathA(
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
698void
699FILEDosToUnixPathA(
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/*++
711Function:
712 FileDosToUnixPathW
713
714Abstract:
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
720Parameter:
721 IN/OUT lpPath: path to be modified
722--*/
723void
724FILEDosToUnixPathW(
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/*++
828Function:
829 FileUnixToDosPathA
830
831Abstract:
832 Change a Unix path to a DOS path. Replace '/' by '\'.
833
834Parameter:
835 IN/OUT lpPath: path to be modified
836--*/
837void
838FILEUnixToDosPathA(
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/*++
859Function:
860 FILEGetDirectoryFromFullPathA
861
862Parse the given path. If it contains a directory part and a file part,
863put the directory part into the supplied buffer, and return the number of
864characters written to the buffer. If the buffer is not large enough,
865return the required size of the buffer including the NULL character. If
866there is no directory part in the path, return 0.
867--*/
868DWORD 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/*++
909Function:
910 FILEGetFileNameFromFullPath
911
912Given a full path, return a pointer to the first char of the filename part.
913--*/
914LPCSTR 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/*++
929FILECanonicalizePath
930 Removes all instances of '/./', '/../' and '//' from an absolute path.
931
932Parameters:
933 LPSTR lpUnixPath : absolute path to modify, in Unix format
934
935(no return value)
936
937Notes :
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--*/
944void 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/*++
1059Function:
1060 SearchPathA
1061
1062See MSDN doc.
1063
1064PAL-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--*/
1070DWORD
1071PALAPI
1072SearchPathA(
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 }
1307done:
1308 LOGEXIT("SearchPathA returns DWORD %u\n", nRet);
1309 PERF_EXIT(SearchPathA);
1310 return nRet;
1311}
1312
1313
1314/*++
1315Function:
1316 SearchPathW
1317
1318See MSDN doc.
1319
1320PAL-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--*/
1326DWORD
1327PALAPI
1328SearchPathW(
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 }
1580done:
1581 LOGEXIT("SearchPathW returns DWORD %u\n", nRet);
1582 PERF_EXIT(SearchPathW);
1583 return nRet;
1584}
1585
1586/*++
1587Function:
1588 PathFindFileNameW
1589
1590See MSDN doc.
1591--*/
1592LPWSTR
1593PALAPI
1594PathFindFileNameW(
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