From 1bdbaed2a7366c2f68265b1e2fd1500a3693d70d Mon Sep 17 00:00:00 2001 From: Alex Ionescu Date: Sun, 6 Nov 2011 17:17:16 +0000 Subject: [PATCH] [KERNEL32]: Rewrite GetLong/ShortPathNameW away from Wine. Some changes: - Windows allows the two input buffers to overlap. This means we have to be very careful with copying (and always use RtlMoveMemory) -- the old function was not handling this at all. - We also have to handle cases where we need to make our own local buffer copy. - Length validation is more stringent now. - Checking for short/long path names wasn't correct w.r.t ANSI file mode, as it was calling Rtl which assumes OEM. - Shortcuts were taken while parsing slashes and path separators. We now call into Rtl to support this, and also support unlimited slashes (note that \\??\c:\\\windows\\\\system32 is actully a valid path, for example). - ErrorMode is now correctly set for the thread, to avoid "Insert floppy" or "Close CDROM bay door" errors as we are using the FindFile API. - Correct LastError is set where appropriate. - An application compatibility flag is now supported. svn path=/trunk/; revision=54318 --- reactos/dll/win32/kernel32/client/path.c | 777 ++++++++++++++++++----- 1 file changed, 620 insertions(+), 157 deletions(-) diff --git a/reactos/dll/win32/kernel32/client/path.c b/reactos/dll/win32/kernel32/client/path.c index 5fc75178984..e8f2767cecb 100644 --- a/reactos/dll/win32/kernel32/client/path.c +++ b/reactos/dll/win32/kernel32/client/path.c @@ -18,8 +18,242 @@ UNICODE_STRING BaseDllDirectory; UNICODE_STRING NoDefaultCurrentDirectoryInExePath = RTL_CONSTANT_STRING(L"NoDefaultCurrentDirectoryInExePath"); +/* This is bitmask for each illegal filename character */ +/* If someone has time, please feel free to use 0b notation */ +DWORD IllegalMask[4] = +{ + 0xFFFFFFFF, // None allowed (00 to 1F) + 0xFC009C05, // 20, 22, 2A, 2B, 2C, 2F, 3A, 3B, 3C, 3D, 3E, 3F not allowed + 0x38000000, // 5B, 5C, 5D not allowed + 0x10000000 // 7C not allowed +}; + /* FUNCTIONS ******************************************************************/ +/* + * Why not use RtlIsNameLegalDOS8Dot3? In fact the actual algorithm body is + * identical (other than the Rtl can optionally check for spaces), however the + * Rtl will always convert to OEM, while kernel32 has two possible file modes + * (ANSI or OEM). Therefore we must duplicate the algorithm body to get + * the correct compatible results + */ +BOOL +WINAPI +IsShortName_U(IN PWCHAR Name, + IN ULONG Length) +{ + BOOLEAN HasExtension; + WCHAR c; + NTSTATUS Status; + UNICODE_STRING UnicodeName; + ANSI_STRING AnsiName; + ULONG i, Dots; + CHAR AnsiBuffer[MAX_PATH]; + ASSERT(Name); + + /* What do you think 8.3 means? */ + if (Length > 12) return FALSE; + + /* Sure, any emtpy name is a short name */ + if (!Length) return TRUE; + + /* This could be . or .. or somethign else */ + if (*Name == L'.') + { + /* Which one is it */ + if ((Length == 1) || ((Length == 2) && *(Name + 1) == L'.')) + { + /* . or .., this is good */ + return TRUE; + } + + /* Some other bizare dot-based name, not good */ + return FALSE; + } + + /* Initialize our two strings */ + RtlInitEmptyAnsiString(&AnsiName, AnsiBuffer, MAX_PATH); + RtlInitEmptyUnicodeString(&UnicodeName, Name, Length * sizeof(WCHAR)); + UnicodeName.Length = UnicodeName.MaximumLength; + + /* Now do the conversion */ + Status = BasepUnicodeStringTo8BitString(&AnsiName, &UnicodeName, FALSE); + if (!NT_SUCCESS(Status)) return FALSE; + + /* Now we loop the name */ + HasExtension = FALSE; + for (i = 0, Dots = Length - 1; i < AnsiName.Length; i++, Dots--) + { + /* Read the current byte */ + c = AnsiName.Buffer[i]; + + /* Is it DBCS? */ + if (IsDBCSLeadByte(c)) + { + /* If we're near the end of the string, we can't allow a DBCS */ + if ((!(HasExtension) && (i >= 7)) || (i == AnsiName.Length - 1)) + { + return FALSE; + } + + /* Otherwise we skip over it */ + continue; + } + + /* Check for illegal characters */ + if ((c > 0x7F) || (IllegalMask[c / 32] && (1 << (c % 32)))) + { + return FALSE; + } + + /* Check if this is perhaps an extension? */ + if (c == L'.') + { + /* Unless the extension is too large or there's more than one */ + if ((HasExtension) || (Dots > 3)) return FALSE; + + /* This looks like an extension */ + HasExtension = TRUE; + } + + /* 8.3 length was validated, but now we must guard against 9.2 or similar */ + if ((i >= 8) && !(HasExtension)) return FALSE; + } + + /* You survived the loop, this is a good short name */ + return TRUE; +} + +BOOL +WINAPI +IsLongName_U(IN PWCHAR FileName, + IN ULONG Length) +{ + BOOLEAN HasExtension; + ULONG i, Dots; + + /* More than 8.3, any combination of dots, and NULL names are all long */ + if (!(Length) || (Length > 12) || (*FileName == L'.')) return TRUE; + + /* Otherwise, initialize our scanning loop */ + HasExtension = FALSE; + for (i = 0, Dots = Length - 1; i < Length; i++, Dots--) + { + /* Check if this could be an extension */ + if (*FileName == '.') + { + /* Unlike the short case, we WANT more than one extension, or a long one */ + if ((HasExtension) || (Dots > 3)) return TRUE; + HasExtension = TRUE; + } + + /* Check if this would violate the "8" in 8.3, ie. 9.2 */ + if ((i >= 8) && (!HasExtension)) return TRUE; + } + + /* The name *seems* to conform to 8.3 */ + return FALSE; +} + +BOOL +WINAPI +FindLFNorSFN_U(IN PWCHAR Path, + OUT PWCHAR *First, + OUT PWCHAR *Last, + IN BOOL UseShort) +{ + PWCHAR p; + ULONG Length; + BOOL Found = 0; + ASSERT(Path); + + /* Loop while there is something in the path */ + while (TRUE) + { + /* Loop within the path skipping slashes */ + while ((*Path) && ((*Path == L'\\') || (*Path == L'/'))) Path++; + + /* Make sure there's something after the slashes too! */ + if (*Path == UNICODE_NULL) break; + + /* Now do the same thing with the last marker */ + p = Path + 1; + while ((*p) && ((*p == L'\\') || (*p == L'/'))) p++; + + /* Whatever is in between those two is now the file name length */ + Length = p - Path; + + /* + * Check if it is valid + * Note that !IsShortName != IsLongName, these two functions simply help + * us determine if a conversion is necessary or not. + */ + Found = UseShort ? IsShortName_U(Path, Length) : IsLongName_U(Path, Length); + + /* "Found" really means: "Is a conversion necessary?", hence the ! */ + if (!Found) + { + /* It is! did the caller request to know the markers? */ + if ((First) && (Last)) + { + /* Return them */ + *First = Path; + *Last = p; + } + break; + } + + /* Is there anything else following this sub-path/filename? */ + if (*p == UNICODE_NULL) break; + + /* Yes, keep going */ + Path = p + 1; + } + + /* Return if anything was found and valid */ + return !Found; +} + +PWCHAR +WINAPI +SkipPathTypeIndicator_U(IN PWCHAR Path) +{ + PWCHAR ReturnPath; + ULONG i; + + /* Check what kind of path this is and how many slashes to skip */ + switch (RtlDetermineDosPathNameType_U(Path)) + { + case RtlPathTypeDriveAbsolute: + return Path + 3; + + case RtlPathTypeDriveRelative: + return Path + 2; + + case RtlPathTypeRooted: + return Path + 1; + + case RtlPathTypeRelative: + return Path; + + case RtlPathTypeRootLocalDevice: + default: + return NULL; + + case RtlPathTypeUncAbsolute: + case RtlPathTypeLocalDevice: + + /* Keep going until we bypass the path indicators */ + for (ReturnPath = Path + 2, i = 2; (i > 0) && (*ReturnPath); ReturnPath++) + { + /* We look for 2 slashes, so keep at it until we find them */ + if ((*ReturnPath == L'\\') || (*ReturnPath == L'/')) i--; + } + + return ReturnPath; + } +} + BOOL WINAPI BasepIsCurDirAllowedForPlainExeNames(VOID) @@ -536,108 +770,221 @@ SearchPathW(LPCWSTR lpPath, return ret; } -/*********************************************************************** +/* * @implemented - * - * GetLongPathNameW (KERNEL32.@) - * - * NOTES - * observed (Win2000): - * shortpath=NULL: LastError=ERROR_INVALID_PARAMETER, ret=0 - * shortpath="": LastError=ERROR_PATH_NOT_FOUND, ret=0 */ -DWORD WINAPI GetLongPathNameW( LPCWSTR shortpath, LPWSTR longpath, DWORD longlen ) +DWORD +WINAPI +GetLongPathNameW(IN LPCWSTR lpszShortPath, + IN LPWSTR lpszLongPath, + IN DWORD cchBuffer) { -#define MAX_PATHNAME_LEN 1024 + PWCHAR Path, Original, First, Last, Buffer, Src, Dst; + ULONG Length; + WCHAR LastChar; + HANDLE FindHandle; + DWORD ReturnLength; + ULONG ErrorMode; + BOOLEAN Found; + WIN32_FIND_DATAW FindFileData; - WCHAR tmplongpath[MAX_PATHNAME_LEN]; - LPCWSTR p; - DWORD sp = 0, lp = 0; - DWORD tmplen; - BOOL unixabsolute; - WIN32_FIND_DATAW wfd; - HANDLE goit; + /* Initialize so Quickie knows there's nothing to do */ + Buffer = Original = NULL; + ReturnLength = 0; - if (!shortpath) + /* First check if the input path was obviously NULL */ + if (!lpszShortPath) { + /* Fail the request */ SetLastError(ERROR_INVALID_PARAMETER); return 0; } - if (!shortpath[0]) + + /* We will be touching removed, removable drives -- don't warn the user */ + ErrorMode = SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS); + + /* Do a simple check to see if the path exists */ + if (GetFileAttributesW(lpszShortPath) == 0xFFFFFFF) { - SetLastError(ERROR_PATH_NOT_FOUND); - return 0; + /* It doesn't, so fail */ + ReturnLength = 0; + goto Quickie; } - DPRINT("GetLongPathNameW(%s,%p,%ld)\n", shortpath, longpath, longlen); + /* Now get a pointer to the actual path, skipping indicators */ + Path = SkipPathTypeIndicator_U((PWCHAR)lpszShortPath); - if (shortpath[0] == '\\' && shortpath[1] == '\\') - { - DPRINT1("ERR: UNC pathname %s\n", shortpath); - lstrcpynW( longpath, shortpath, longlen ); - return wcslen(longpath); - } - unixabsolute = (shortpath[0] == '/'); - /* check for drive letter */ - if (!unixabsolute && shortpath[1] == ':' ) - { - tmplongpath[0] = shortpath[0]; - tmplongpath[1] = ':'; - lp = sp = 2; - } + /* Try to find a file name in there */ + if (Path) Found = FindLFNorSFN_U(Path, &First, &Last, FALSE); - while (shortpath[sp]) + /* Is there any path or filename in there? */ + if (!(Path) || (*Path == UNICODE_NULL) || !(Found)) { - /* check for path delimiters and reproduce them */ - if (shortpath[sp] == '\\' || shortpath[sp] == '/') + /* There isn't, so the long path is simply the short path */ + ReturnLength = wcslen(lpszShortPath); + + /* Is there space for it? */ + if ((cchBuffer > ReturnLength) && (lpszLongPath)) { - if (!lp || tmplongpath[lp-1] != '\\') + /* Make sure the pointers aren't already the same */ + if (lpszLongPath != lpszShortPath) { - /* strip double "\\" */ - tmplongpath[lp++] = '\\'; + /* They're not -- copy the short path into the long path */ + RtlMoveMemory(lpszLongPath, + lpszShortPath, + ReturnLength * sizeof(WCHAR) + sizeof(UNICODE_NULL)); } - tmplongpath[lp] = 0; /* terminate string */ - sp++; - continue; } - - p = shortpath + sp; - if (sp == 0 && p[0] == '.' && (p[1] == '/' || p[1] == '\\')) + else { - tmplongpath[lp++] = *p++; - tmplongpath[lp++] = *p++; + /* Otherwise, let caller know we need a bigger buffer, include NULL */ + ReturnLength++; } - for (; *p && *p != '/' && *p != '\\'; p++); - tmplen = p - (shortpath + sp); - lstrcpynW(tmplongpath + lp, shortpath + sp, tmplen + 1); - /* Check if the file exists and use the existing file name */ - goit = FindFirstFileW(tmplongpath, &wfd); - if (goit == INVALID_HANDLE_VALUE) - { - DPRINT("not found %s!\n", tmplongpath); - SetLastError ( ERROR_FILE_NOT_FOUND ); - return 0; - } - FindClose(goit); - wcscpy(tmplongpath + lp, wfd.cFileName); - lp += wcslen(tmplongpath + lp); - sp += tmplen; + goto Quickie; } - tmplen = wcslen(shortpath) - 1; - if ((shortpath[tmplen] == '/' || shortpath[tmplen] == '\\') && - (tmplongpath[lp - 1] != '/' && tmplongpath[lp - 1] != '\\')) - tmplongpath[lp++] = shortpath[tmplen]; - tmplongpath[lp] = 0; - tmplen = wcslen(tmplongpath) + 1; - if (tmplen <= longlen) + /* We are still in the game -- compute the current size */ + Length = wcslen(lpszShortPath) + sizeof(ANSI_NULL); + Original = RtlAllocateHeap(RtlGetProcessHeap(), 0, Length * sizeof(WCHAR)); + if (!Original) goto ErrorQuickie; + + /* Make a copy ofi t */ + RtlMoveMemory(Original, lpszShortPath, Length * sizeof(WCHAR)); + + /* Compute the new first and last markers */ + First = &Original[First - lpszShortPath]; + Last = &Original[Last - lpszShortPath]; + + /* Set the current destination pointer for a copy */ + Dst = lpszLongPath; + + /* + * Windows allows the paths to overlap -- we have to be careful with this and + * see if it's same to do so, and if not, allocate our own internal buffer + * that we'll return at the end. + * + * This is also why we use RtlMoveMemory everywhere. Don't use RtlCopyMemory! + */ + if ((cchBuffer) && (lpszLongPath) && + (((lpszLongPath >= lpszShortPath) && (lpszLongPath < &lpszShortPath[Length])) || + ((lpszLongPath < lpszShortPath) && (&lpszLongPath[cchBuffer] >= lpszShortPath)))) { - wcscpy(longpath, tmplongpath); - DPRINT("returning %s\n", longpath); - tmplen--; /* length without 0 */ + Buffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, cchBuffer * sizeof(WCHAR)); + if (!Buffer) goto ErrorQuickie; + + /* New destination */ + Dst = Buffer; } - return tmplen; + /* Prepare for the loop */ + Src = Original; + ReturnLength = 0; + while (TRUE) + { + /* Current delta in the loop */ + Length = First - Src; + + /* Update the return length by it */ + ReturnLength += Length; + + /* Is there a delta? If so, is there space and buffer for it? */ + if ((Length) && (cchBuffer > ReturnLength) && (lpszLongPath)) + { + RtlMoveMemory(Dst, Src, Length * sizeof(WCHAR)); + Dst += Length; + } + + /* "Terminate" this portion of the path's substring so we can do a find */ + LastChar = *Last; + *Last = UNICODE_NULL; + FindHandle = FindFirstFileW(Original, &FindFileData); + *Last = LastChar; + + /* This portion wasn't found, so fail */ + if (FindHandle == INVALID_HANDLE_VALUE) + { + ReturnLength = 0; + break; + } + + /* Close the find handle */ + FindClose(FindHandle); + + /* Now check the length of the long name */ + Length = wcslen(FindFileData.cFileName); + if (Length) + { + /* This is our new first marker */ + First = FindFileData.cFileName; + } + else + { + /* Otherwise, the name is the delta between our current markers */ + Length = Last - First; + } + + /* Update the return length with the short name length, if any */ + ReturnLength += Length; + + /* Once again check for appropriate space and buffer */ + if ((cchBuffer > ReturnLength) && (lpszLongPath)) + { + /* And do the copy if there is */ + RtlMoveMemory(Dst, First, Length * sizeof(WCHAR)); + Dst += Length; + } + + /* Now update the source pointer */ + Src = Last; + if (*Src == UNICODE_NULL) break; + + /* Are there more names in there? */ + Found = FindLFNorSFN_U(Src, &First, &Last, FALSE); + if (!Found) break; + } + + /* The loop is done, is there anything left? */ + if (ReturnLength) + { + /* Get the length of the straggling path */ + Length = wcslen(Src); + ReturnLength += Length; + + /* Once again check for appropriate space and buffer */ + if ((cchBuffer > ReturnLength) && (lpszLongPath)) + { + /* And do the copy if there is -- accounting for NULL here */ + RtlMoveMemory(Dst, Src, Length * sizeof(WCHAR) + sizeof(UNICODE_NULL)); + + /* What about our buffer? */ + if (Buffer) + { + /* Copy it into the caller's long path */ + RtlMoveMemory(lpszLongPath, + Buffer, + ReturnLength * sizeof(WCHAR) + sizeof(UNICODE_NULL)); + } + } + else + { + /* Buffer is too small, let the caller know, making space for NULL */ + ReturnLength++; + } + } + + /* We're all done */ + goto Quickie; + +ErrorQuickie: + /* This is the goto for memory failures */ + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + +Quickie: + /* General function end: free memory, restore error mode, return length */ + if (Original) RtlFreeHeap(RtlGetProcessHeap(), 0, Original); + if (Buffer) RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer); + SetErrorMode(ErrorMode); + return ReturnLength; } /* @@ -660,13 +1007,13 @@ GetLongPathNameA(IN LPCSTR lpszShortPath, LongPathAnsi.Buffer = NULL; ShortPathUni.Buffer = NULL; Result = 0; - + if (!lpszShortPath) { SetLastError(ERROR_INVALID_PARAMETER); return 0; } - + Status = Basep8BitStringToDynamicUnicodeString(&ShortPathUni, lpszShortPath); if (!NT_SUCCESS(Status)) goto Quickie; @@ -686,7 +1033,7 @@ GetLongPathNameA(IN LPCSTR lpszShortPath, PathLength = GetLongPathNameW(ShortPathUni.Buffer, LongPath, PathLength); } } - + if (!PathLength) goto Quickie; ShortPathUni.MaximumLength = PathLength * sizeof(WCHAR) + sizeof(UNICODE_NULL); @@ -741,13 +1088,13 @@ GetShortPathNameA(IN LPCSTR lpszLongPath, ShortPathAnsi.Buffer = NULL; LongPathUni.Buffer = NULL; Result = 0; - + if (!lpszLongPath) { SetLastError(ERROR_INVALID_PARAMETER); return 0; } - + Status = Basep8BitStringToDynamicUnicodeString(&LongPathUni, lpszLongPath); if (!NT_SUCCESS(Status)) goto Quickie; @@ -767,7 +1114,7 @@ GetShortPathNameA(IN LPCSTR lpszLongPath, PathLength = GetLongPathNameW(LongPathUni.Buffer, ShortPath, PathLength); } } - + if (!PathLength) goto Quickie; LongPathUni.MaximumLength = PathLength * sizeof(WCHAR) + sizeof(UNICODE_NULL); @@ -802,109 +1149,225 @@ Quickie: return Result; } - /* - * NOTE: Copied from Wine. * @implemented */ DWORD WINAPI -GetShortPathNameW ( - LPCWSTR longpath, - LPWSTR shortpath, - DWORD shortlen - ) +GetShortPathNameW(IN LPCWSTR lpszLongPath, + IN LPWSTR lpszShortPath, + IN DWORD cchBuffer) { - WCHAR tmpshortpath[MAX_PATH]; - LPCWSTR p; - DWORD sp = 0, lp = 0; - DWORD tmplen; - WIN32_FIND_DATAW wfd; - HANDLE goit; - UNICODE_STRING ustr; - WCHAR ustr_buf[8+1+3+1]; + PWCHAR Path, Original, First, Last, Buffer, Src, Dst; + ULONG Length; + WCHAR LastChar; + HANDLE FindHandle; + DWORD ReturnLength; + ULONG ErrorMode; + BOOLEAN Found; + WIN32_FIND_DATAW FindFileData; - DPRINT("GetShortPathNameW: %S\n",longpath); + /* Initialize so Quickie knows there's nothing to do */ + Buffer = Original = NULL; + ReturnLength = 0; - if (!longpath) + /* First check if the input path was obviously NULL */ + if (!lpszLongPath) { + /* Fail the request */ SetLastError(ERROR_INVALID_PARAMETER); return 0; } - if (!longpath[0]) - { - SetLastError(ERROR_BAD_PATHNAME); - return 0; - } - /* check for drive letter */ - if (longpath[0] != '/' && longpath[1] == ':' ) - { - tmpshortpath[0] = longpath[0]; - tmpshortpath[1] = ':'; - sp = lp = 2; - } + /* We will be touching removed, removable drives -- don't warn the user */ + ErrorMode = SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS); - ustr.Buffer = ustr_buf; - ustr.Length = 0; - ustr.MaximumLength = sizeof(ustr_buf); - - while (longpath[lp]) + /* Do a simple check to see if the path exists */ + if (GetFileAttributesW(lpszShortPath) == 0xFFFFFFF) { - /* check for path delimiters and reproduce them */ - if (longpath[lp] == '\\' || longpath[lp] == '/') + /* Windows checks for an application compatibility flag to allow this */ + if (!(NtCurrentPeb()) || !(NtCurrentPeb()->AppCompatFlags.LowPart & 1)) { - if (!sp || tmpshortpath[sp-1] != '\\') - { - /* strip double "\\" */ - tmpshortpath[sp] = '\\'; - sp++; - } - tmpshortpath[sp] = 0; /* terminate string */ - lp++; - continue; + /* It doesn't, so fail */ + ReturnLength = 0; + goto Quickie; } + } - for (p = longpath + lp; *p && *p != '/' && *p != '\\'; p++); - tmplen = p - (longpath + lp); - lstrcpynW(tmpshortpath + sp, longpath + lp, tmplen + 1); - /* Check, if the current element is a valid dos name */ - if (tmplen <= 8+1+3) + /* Now get a pointer to the actual path, skipping indicators */ + Path = SkipPathTypeIndicator_U((PWCHAR)lpszShortPath); + + /* Try to find a file name in there */ + if (Path) Found = FindLFNorSFN_U(Path, &First, &Last, TRUE); + + /* Is there any path or filename in there? */ + if (!(Path) || (*Path == UNICODE_NULL) || !(Found)) + { + /* There isn't, so the long path is simply the short path */ + ReturnLength = wcslen(lpszLongPath); + + /* Is there space for it? */ + if ((cchBuffer > ReturnLength) && (lpszLongPath)) { - BOOLEAN spaces; - memcpy(ustr_buf, longpath + lp, tmplen * sizeof(WCHAR)); - ustr_buf[tmplen] = '\0'; - ustr.Length = (USHORT)tmplen * sizeof(WCHAR); - if (RtlIsNameLegalDOS8Dot3(&ustr, NULL, &spaces) && !spaces) + /* Make sure the pointers aren't already the same */ + if (lpszLongPath != lpszShortPath) { - sp += tmplen; - lp += tmplen; - continue; + /* They're not -- copy the short path into the long path */ + RtlMoveMemory(lpszShortPath, + lpszLongPath, + ReturnLength * sizeof(WCHAR) + sizeof(UNICODE_NULL)); } } - - /* Check if the file exists and use the existing short file name */ - goit = FindFirstFileW(tmpshortpath, &wfd); - if (goit == INVALID_HANDLE_VALUE) goto notfound; - FindClose(goit); - lstrcpyW(tmpshortpath + sp, wfd.cAlternateFileName); - sp += lstrlenW(tmpshortpath + sp); - lp += tmplen; + else + { + /* Otherwise, let caller know we need a bigger buffer, include NULL */ + ReturnLength++; + } + goto Quickie; } - tmpshortpath[sp] = 0; - tmplen = lstrlenW(tmpshortpath) + 1; - if (tmplen <= shortlen) + /* We are still in the game -- compute the current size */ + Length = wcslen(lpszLongPath) + sizeof(ANSI_NULL); + Original = RtlAllocateHeap(RtlGetProcessHeap(), 0, Length * sizeof(WCHAR)); + if (!Original) goto ErrorQuickie; + + /* Make a copy of it */ + wcsncpy(Original, lpszLongPath, Length); + + /* Compute the new first and last markers */ + First = &Original[First - lpszLongPath]; + Last = &Original[Last - lpszLongPath]; + + /* Set the current destination pointer for a copy */ + Dst = lpszShortPath; + + /* + * Windows allows the paths to overlap -- we have to be careful with this and + * see if it's same to do so, and if not, allocate our own internal buffer + * that we'll return at the end. + * + * This is also why we use RtlMoveMemory everywhere. Don't use RtlCopyMemory! + */ + if ((cchBuffer) && (lpszShortPath) && + (((lpszShortPath >= lpszLongPath) && (lpszShortPath < &lpszLongPath[Length])) || + ((lpszShortPath < lpszLongPath) && (&lpszShortPath[cchBuffer] >= lpszLongPath)))) { - lstrcpyW(shortpath, tmpshortpath); - tmplen--; /* length without 0 */ + Buffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, cchBuffer * sizeof(WCHAR)); + if (!Buffer) goto ErrorQuickie; + + /* New destination */ + Dst = Buffer; } - return tmplen; + /* Prepare for the loop */ + Src = Original; + ReturnLength = 0; + while (TRUE) + { + /* Current delta in the loop */ + Length = First - Src; - notfound: - SetLastError ( ERROR_FILE_NOT_FOUND ); - return 0; + /* Update the return length by it */ + ReturnLength += Length; + + /* Is there a delta? If so, is there space and buffer for it? */ + if ((Length) && (cchBuffer > ReturnLength) && (lpszShortPath)) + { + RtlMoveMemory(Dst, Src, Length * sizeof(WCHAR)); + Dst += Length; + } + + /* "Terminate" this portion of the path's substring so we can do a find */ + LastChar = *Last; + *Last = UNICODE_NULL; + FindHandle = FindFirstFileW(Original, &FindFileData); + *Last = LastChar; + + /* This portion wasn't found, so fail */ + if (FindHandle == INVALID_HANDLE_VALUE) + { + ReturnLength = 0; + break; + } + + /* Close the find handle */ + FindClose(FindHandle); + + /* Now check the length of the short name */ + Length = wcslen(FindFileData.cAlternateFileName); + if (Length) + { + /* This is our new first marker */ + First = FindFileData.cAlternateFileName; + } + else + { + /* Otherwise, the name is the delta between our current markers */ + Length = Last - First; + } + + /* Update the return length with the short name length, if any */ + ReturnLength += Length; + + /* Once again check for appropriate space and buffer */ + if ((cchBuffer > ReturnLength) && (lpszShortPath)) + { + /* And do the copy if there is */ + RtlMoveMemory(Dst, First, Length * sizeof(WCHAR)); + Dst += Length; + } + + /* Now update the source pointer */ + Src = Last; + if (*Src == UNICODE_NULL) break; + + /* Are there more names in there? */ + Found = FindLFNorSFN_U(Src, &First, &Last, TRUE); + if (!Found) break; + } + + /* The loop is done, is there anything left? */ + if (ReturnLength) + { + /* Get the length of the straggling path */ + Length = wcslen(Src); + ReturnLength += Length; + + /* Once again check for appropriate space and buffer */ + if ((cchBuffer > ReturnLength) && (lpszShortPath)) + { + /* And do the copy if there is -- accounting for NULL here */ + RtlMoveMemory(Dst, Src, Length * sizeof(WCHAR) + sizeof(UNICODE_NULL)); + + /* What about our buffer? */ + if (Buffer) + { + /* Copy it into the caller's long path */ + RtlMoveMemory(lpszShortPath, + Buffer, + ReturnLength * sizeof(WCHAR) + sizeof(UNICODE_NULL)); + } + } + else + { + /* Buffer is too small, let the caller know, making space for NULL */ + ReturnLength++; + } + } + + /* We're all done */ + goto Quickie; + +ErrorQuickie: + /* This is the goto for memory failures */ + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + +Quickie: + /* General function end: free memory, restore error mode, return length */ + if (Original) RtlFreeHeap(RtlGetProcessHeap(), 0, Original); + if (Buffer) RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer); + SetErrorMode(ErrorMode); + return ReturnLength; } /* EOF */