[ros-diffs] [ion] 54318: [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 alwa...

ion at svn.reactos.org ion at svn.reactos.org
Sun Nov 6 17:17:17 UTC 2011


Author: ion
Date: Sun Nov  6 17:17:16 2011
New Revision: 54318

URL: http://svn.reactos.org/svn/reactos?rev=54318&view=rev
Log:
[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.

Modified:
    trunk/reactos/dll/win32/kernel32/client/path.c

Modified: trunk/reactos/dll/win32/kernel32/client/path.c
URL: http://svn.reactos.org/svn/reactos/trunk/reactos/dll/win32/kernel32/client/path.c?rev=54318&r1=54317&r2=54318&view=diff
==============================================================================
--- trunk/reactos/dll/win32/kernel32/client/path.c [iso-8859-1] (original)
+++ trunk/reactos/dll/win32/kernel32/client/path.c [iso-8859-1] Sun Nov  6 17:17:16 2011
@@ -18,7 +18,241 @@
 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
@@ -536,108 +770,221 @@
     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 )
-{
-#define    MAX_PATHNAME_LEN 1024
-
-    WCHAR               tmplongpath[MAX_PATHNAME_LEN];
-    LPCWSTR             p;
-    DWORD               sp = 0, lp = 0;
-    DWORD               tmplen;
-    BOOL                unixabsolute;
-    WIN32_FIND_DATAW    wfd;
-    HANDLE              goit;
-
-    if (!shortpath)
-    {
+ */
+DWORD
+WINAPI
+GetLongPathNameW(IN LPCWSTR lpszShortPath,
+                 IN LPWSTR lpszLongPath,
+                 IN DWORD cchBuffer)
+{
+    PWCHAR Path, Original, First, Last, Buffer, Src, Dst;
+    ULONG Length;
+    WCHAR LastChar;
+    HANDLE FindHandle;
+    DWORD ReturnLength;
+    ULONG ErrorMode;
+    BOOLEAN Found;
+    WIN32_FIND_DATAW FindFileData;
+
+    /* Initialize so Quickie knows there's nothing to do */
+    Buffer = Original = NULL;
+    ReturnLength = 0;
+
+    /* First check if the input path was obviously NULL */
+    if (!lpszShortPath)
+    {
+        /* Fail the request */
         SetLastError(ERROR_INVALID_PARAMETER);
         return 0;
     }
-    if (!shortpath[0])
-    {
-        SetLastError(ERROR_PATH_NOT_FOUND);
-        return 0;
-    }
-
-    DPRINT("GetLongPathNameW(%s,%p,%ld)\n", shortpath, longpath, longlen);
-
-    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;
-    }
-
-    while (shortpath[sp])
-    {
-        /* check for path delimiters and reproduce them */
-        if (shortpath[sp] == '\\' || shortpath[sp] == '/')
-        {
-            if (!lp || tmplongpath[lp-1] != '\\')
+
+    /* 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)
+    {
+        /* It doesn't, so fail */
+        ReturnLength = 0;
+        goto Quickie;
+    }
+
+    /* 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, FALSE);
+
+    /* 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(lpszShortPath);
+
+        /* Is there space for it? */
+        if ((cchBuffer > ReturnLength) && (lpszLongPath))
+        {
+            /* 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] == '\\'))
-        {
-            tmplongpath[lp++] = *p++;
-            tmplongpath[lp++] = *p++;
-        }
-        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;
-    }
-    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)
-    {
-        wcscpy(longpath, tmplongpath);
-        DPRINT("returning %s\n", longpath);
-        tmplen--; /* length without 0 */
-    }
-
-    return tmplen;
+        }
+        else
+        {
+            /* Otherwise, let caller know we need a bigger buffer, include NULL */
+            ReturnLength++;
+        }
+        goto Quickie;
+    }
+
+    /* 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))))
+    {
+        Buffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, cchBuffer * sizeof(WCHAR));
+        if (!Buffer) goto ErrorQuickie;
+
+        /* New destination */
+        Dst = Buffer;
+    }
+
+    /* 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 @@
     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 @@
             PathLength = GetLongPathNameW(ShortPathUni.Buffer, LongPath, PathLength);
         }
     }
-        
+
     if (!PathLength) goto Quickie;
 
     ShortPathUni.MaximumLength = PathLength * sizeof(WCHAR) + sizeof(UNICODE_NULL);
@@ -741,13 +1088,13 @@
     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 @@
             PathLength = GetLongPathNameW(LongPathUni.Buffer, ShortPath, PathLength);
         }
     }
-        
+
     if (!PathLength) goto Quickie;
 
     LongPathUni.MaximumLength = PathLength * sizeof(WCHAR) + sizeof(UNICODE_NULL);
@@ -802,109 +1149,225 @@
     return Result;
 }
 
-
-/*
- * NOTE: Copied from Wine.
+/*
  * @implemented
  */
 DWORD
 WINAPI
-GetShortPathNameW (
-        LPCWSTR longpath,
-        LPWSTR  shortpath,
-        DWORD   shortlen
-        )
-{
-    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];
-
-   DPRINT("GetShortPathNameW: %S\n",longpath);
-
-    if (!longpath)
-    {
+GetShortPathNameW(IN LPCWSTR lpszLongPath,
+                  IN LPWSTR lpszShortPath,
+                  IN DWORD cchBuffer)
+{
+    PWCHAR Path, Original, First, Last, Buffer, Src, Dst;
+    ULONG Length;
+    WCHAR LastChar;
+    HANDLE FindHandle;
+    DWORD ReturnLength;
+    ULONG ErrorMode;
+    BOOLEAN Found;
+    WIN32_FIND_DATAW FindFileData;
+
+    /* Initialize so Quickie knows there's nothing to do */
+    Buffer = Original = NULL;
+    ReturnLength = 0;
+
+    /* 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;
-    }
-
-    ustr.Buffer = ustr_buf;
-    ustr.Length = 0;
-    ustr.MaximumLength = sizeof(ustr_buf);
-
-    while (longpath[lp])
-    {
-        /* check for path delimiters and reproduce them */
-        if (longpath[lp] == '\\' || longpath[lp] == '/')
-        {
-            if (!sp || tmpshortpath[sp-1] != '\\')
+
+    /* 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)
+    {
+        /* Windows checks for an application compatibility flag to allow this */
+        if (!(NtCurrentPeb()) || !(NtCurrentPeb()->AppCompatFlags.LowPart & 1))
+        {
+            /* It doesn't, so fail */
+            ReturnLength = 0;
+            goto Quickie;
+        }
+    }
+
+    /* 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))
+        {
+            /* Make sure the pointers aren't already the same */
+            if (lpszLongPath != lpszShortPath)
             {
-                /* strip double "\\" */
-                tmpshortpath[sp] = '\\';
-                sp++;
+                /* They're not -- copy the short path into the long path */
+                RtlMoveMemory(lpszShortPath,
+                              lpszLongPath,
+                              ReturnLength * sizeof(WCHAR) + sizeof(UNICODE_NULL));
             }
-            tmpshortpath[sp] = 0; /* terminate string */
-            lp++;
-            continue;
-        }
-
-        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)
-        {
-            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)
+        }
+        else
+        {
+            /* Otherwise, let caller know we need a bigger buffer, include NULL */
+            ReturnLength++;
+        }
+        goto Quickie;
+    }
+
+    /* 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))))
+    {
+        Buffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, cchBuffer * sizeof(WCHAR));
+        if (!Buffer) goto ErrorQuickie;
+
+        /* New destination */
+        Dst = Buffer;
+    }
+
+    /* 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) && (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)
             {
-                sp += tmplen;
-                lp += tmplen;
-                continue;
+                /* Copy it into the caller's long path */
+                RtlMoveMemory(lpszShortPath,
+                              Buffer,
+                              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;
-    }
-    tmpshortpath[sp] = 0;
-
-    tmplen = lstrlenW(tmpshortpath) + 1;
-    if (tmplen <= shortlen)
-    {
-        lstrcpyW(shortpath, tmpshortpath);
-        tmplen--; /* length without 0 */
-    }
-
-    return tmplen;
-
- notfound:
-    SetLastError ( ERROR_FILE_NOT_FOUND );
-    return 0;
+        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 */




More information about the Ros-diffs mailing list