Fixed RtlGetFullPathName_U bug (didn't remove duplicate slashes) and general ugliness by rippin fresh version from wine. Makes w2k cmd.exe work (imo) 100%.
Modified: trunk/reactos/include/ntdll/rtl.h
Modified: trunk/reactos/lib/ntdll/rtl/path.c

Modified: trunk/reactos/include/ntdll/rtl.h
--- trunk/reactos/include/ntdll/rtl.h	2005-01-07 13:48:53 UTC (rev 12868)
+++ trunk/reactos/include/ntdll/rtl.h	2005-01-07 15:07:52 UTC (rev 12869)
@@ -17,6 +17,13 @@
 
 #ifndef __USE_W32API
 
+#define RTL_CONSTANT_STRING(__SOURCE_STRING__) \
+{ \
+ sizeof(__SOURCE_STRING__) - sizeof((__SOURCE_STRING__)[0]), \
+ sizeof(__SOURCE_STRING__), \
+ (__SOURCE_STRING__) \
+}
+
 typedef struct _DEBUG_BUFFER
 {
   HANDLE SectionHandle;
@@ -355,7 +362,7 @@
 ULONG
 STDCALL
 RtlGetFullPathName_U (
-	WCHAR *dosname,
+   const WCHAR *dosname,
 	ULONG size,
 	WCHAR *buf,
 	WCHAR **shortname

Modified: trunk/reactos/lib/ntdll/rtl/path.c
--- trunk/reactos/lib/ntdll/rtl/path.c	2005-01-07 13:48:53 UTC (rev 12868)
+++ trunk/reactos/lib/ntdll/rtl/path.c	2005-01-07 15:07:52 UTC (rev 12869)
@@ -12,9 +12,11 @@
 
 #include <ddk/ntddk.h>
 #include <ntdll/rtl.h>
+#include <ntos/minmax.h>
 #include <string.h>
 #include <stdio.h>
 #include <ctype.h>
+#include <base.h>
 #include <ddk/obfuncs.h>
 
 #define NDEBUG
@@ -26,142 +28,28 @@
 
 #define IS_PATH_SEPARATOR(x) (((x)==L'\\')||((x)==L'/'))
 
+
 /* GLOBALS ********************************************************************/
 
-static const UNICODE_STRING _condev = 
-{
-    .Length	    = sizeof(L"\\\\.\\CON") - sizeof(WCHAR),
-    .MaximumLength  = sizeof(L"\\\\.\\CON"),
-    .Buffer	    = L"\\\\.\\CON"
-};
+static const WCHAR DeviceRootW[] = L"\\\\.\\";
 
-static const UNICODE_STRING _lpt =
-{
-    .Length	    = sizeof(L"LPT") - sizeof(WCHAR),
-    .MaximumLength  = sizeof(L"LPT"),
-    .Buffer	    = L"LPT"
-};
+static const UNICODE_STRING _condev = RTL_CONSTANT_STRING(L"\\\\.\\CON");
 
-static const UNICODE_STRING _com =
-{
-    .Length	    = sizeof(L"COM") - sizeof(WCHAR),
-    .MaximumLength  = sizeof(L"COM"),
-    .Buffer	    = L"COM"
-};
+static const UNICODE_STRING _lpt = RTL_CONSTANT_STRING(L"LPT");
 
-static const UNICODE_STRING _prn =
-{
-    .Length	    = sizeof(L"PRN") - sizeof(WCHAR),
-    .MaximumLength  = sizeof(L"PRN"),
-    .Buffer	    = L"PRN"
-};
+static const UNICODE_STRING _com = RTL_CONSTANT_STRING(L"COM");
 
-static const UNICODE_STRING _aux =
-{
-    .Length	    = sizeof(L"AUX") - sizeof(WCHAR),
-    .MaximumLength  = sizeof(L"AUX"),
-    .Buffer	    = L"AUX"
-};
+static const UNICODE_STRING _prn = RTL_CONSTANT_STRING(L"PRN");
 
-static const UNICODE_STRING _con =
-{
-    .Length	    = sizeof(L"CON") - sizeof(WCHAR),
-    .MaximumLength  = sizeof(L"CON"),
-    .Buffer	    = L"CON"
-};
+static const UNICODE_STRING _aux = RTL_CONSTANT_STRING(L"AUX");
 
-static const UNICODE_STRING _nul =
-{
-    .Length	    = sizeof(L"NUL") - sizeof(WCHAR),
-    .MaximumLength  = sizeof(L"NUL"),
-    .Buffer	    = L"NUL"
-};
+static const UNICODE_STRING _con = RTL_CONSTANT_STRING(L"CON");
 
+static const UNICODE_STRING _nul = RTL_CONSTANT_STRING(L"NUL");
+
 /* FUNCTIONS *****************************************************************/
 
-static ULONG RtlpGetDotSequence (PWSTR p)
-{
-   ULONG Count = 0;
-   
-   for (;;)
-     {
-	if (*p == '.')
-	  Count++;
-	else if ((*p == '\\' || *p == '\0') && Count)
-	  return Count;
-	else
-	  return 0;
-	p++;
-     }
-   return 0;
-}
 
-
-static VOID RtlpEatPath (PWSTR Path)
-{
-   PWSTR p, prev;
-   
-   p = Path + 2;
-   prev = p;
-   
-   while ((*p) != 0 || ((*p) == L'\\' && (*(p+1)) == 0))
-     {
-	ULONG DotLen;
-	
-	DotLen = RtlpGetDotSequence (p+1);
-	DPRINT("DotSequenceLength %u\n", DotLen);
-	DPRINT("prev '%S' p '%S'\n",prev,p);
-
-        if (DotLen == 0)
-	  {
-	     prev = p;
-	     p = wcschr(p + 1, L'\\');
-	     if (p == NULL)
-	     {
-		 break;
-	     }
-	  }
-	else if (DotLen == 1)
-	  {
-	     wcscpy (p, p+2);
-	  }
-	else
-	  {
-	     if (DotLen > 2)
-	       {
-		  int n = DotLen - 2;
-		  
-		  while (n > 0 && prev > (Path + 2))
-		    {
-		       prev--;
-		       if ((*prev) == L'\\')
-			 n--;
-		    }
-	       }
-	     
-	     if (*(p + DotLen + 1) == 0)
-	       *(prev + 1) = 0;
-			else
-	       wcscpy (prev, p + DotLen + 1);
-	     p = prev;
-	     if (prev > (Path + 2))
-	       {
-		  prev--;
-		  while ((*prev) != L'\\')
-		    {
-		       prev--;
-		    }
-	       }
-	  }
-     }
-     if (Path[2] == 0)
-     {
-        Path[2] = L'\\';
-	Path[3] = 0;
-     }
-}
-
-
 /*
  * @implemented
  */
@@ -532,224 +420,338 @@
    return STATUS_SUCCESS;
 }
 
-/*
- * @implemented
+
+
+/******************************************************************
+ *    collapse_path
+ *
+ * Helper for RtlGetFullPathName_U.
+ * 1) Convert slashes into backslashes
+ * 2) Get rid of duplicate backslashes
+ * 3) Get rid of . and .. components in the path.
  */
-ULONG STDCALL
-RtlGetFullPathName_U(PWSTR DosName,
-		     ULONG size,
-		     PWSTR buf,
-		     PWSTR *FilePart)
+static inline void collapse_path( WCHAR *path, UINT mark )
 {
-	WCHAR           *wcs, var[4], drive;
-	ULONG           len;
-	ULONG		templen = 0;
-	DWORD           offs, sz, type;
-	UNICODE_STRING  usvar, pfx;
-	PCURDIR cd;
-	NTSTATUS Status;
-	WCHAR TempFullPathName[MAX_PATH] = L"";
+    WCHAR *p, *next;
 
-	DPRINT("RtlGetFullPathName_U %S %ld %p %p\n",
-	       DosName, size, buf, FilePart);
+    /* convert every / into a \ */
+    for (p = path; *p; p++) if (*p == '/') *p = '\\';
 
-  /* FIXME: this code is a mess! We should use Wine's implementation
-  of this function, since it's cleaner and IMO better. -Gunnar */
-  
-	if (!DosName || !*DosName)
-		return 0;
+    /* collapse duplicate backslashes */
+    next = path + max( 1, mark );
+    for (p = next; *p; p++) if (*p != '\\' || next[-1] != '\\') *next++ = *p;
+    *next = 0;
 
-	len = wcslen (DosName);
+    p = path + mark;
+    while (*p)
+    {
+        if (*p == '.')
+        {
+            switch(p[1])
+            {
+            case '\\': /* .\ component */
+                next = p + 2;
+                memmove( p, next, (wcslen(next) + 1) * sizeof(WCHAR) );
+                continue;
+            case 0:  /* final . */
+                if (p > path + mark) p--;
+                *p = 0;
+                continue;
+            case '.':
+                if (p[2] == '\\')  /* ..\ component */
+                {
+                    next = p + 3;
+                    if (p > path + mark)
+                    {
+                        p--;
+                        while (p > path + mark && p[-1] != '\\') p--;
+                    }
+                    memmove( p, next, (wcslen(next) + 1) * sizeof(WCHAR) );
+                    continue;
+                }
+                else if (!p[2])  /* final .. */
+                {
+                    if (p > path + mark)
+                    {
+                        p--;
+                        while (p > path + mark && p[-1] != '\\') p--;
+                        if (p > path + mark) p--;
+                    }
+                    *p = 0;
+                    continue;
+                }
+                break;
+            }
+        }
+        /* skip to the next component */
+        while (*p && *p != '\\') p++;
+        if (*p == '\\') p++;
+    }
 
-	/* strip trailing spaces */
-	while (len && DosName[len - 1] == L' ')
-		len--;
-	if (!len)
-		return 0;
-	
-	/* strip trailing path separator (but don't change '\') */
-	if ((len > 1) &&
-	    IS_PATH_SEPARATOR(DosName[len - 1]))
-		len--;
-	if (FilePart)
-		*FilePart = NULL;
-	if (buf)
-		*buf = 0;
+    /* remove trailing spaces and dots (yes, Windows really does that, don't ask) */
+    while (p > path + mark && (p[-1] == ' ' || p[-1] == '.')) p--;
+    *p = 0;
+}
 
-CHECKPOINT;
-	/* check for DOS device name */
-	sz = RtlIsDosDeviceName_U (DosName);
-	if (sz)
-	{
-		offs = sz >> 17;
-		sz &= 0x0000FFFF;
-		if (sz + 8 >= size)
-		    return sz + 10;
-		if (buf)
-		{
-			wcscpy (buf, L"\\\\.\\");
-			wcsncat (buf, DosName + offs, sz / sizeof(WCHAR));
-		}
-		return sz + 8;
-	}
 
-CHECKPOINT;
-	type = RtlDetermineDosPathNameType_U (DosName);
 
-	RtlAcquirePebLock();
+/******************************************************************
+ *    skip_unc_prefix
+ *
+ * Skip the \\share\dir\ part of a file name. Helper for RtlGetFullPathName_U.
+ */
+static const WCHAR *skip_unc_prefix( const WCHAR *ptr )
+{
+    ptr += 2;
+    while (*ptr && !IS_PATH_SEPARATOR(*ptr)) ptr++;  /* share name */
+    while (IS_PATH_SEPARATOR(*ptr)) ptr++;
+    while (*ptr && !IS_PATH_SEPARATOR(*ptr)) ptr++;  /* dir name */
+    while (IS_PATH_SEPARATOR(*ptr)) ptr++;
+    return ptr;
+}
 
-	cd = (PCURDIR)&(NtCurrentPeb ()->ProcessParameters->CurrentDirectoryName);
-DPRINT("type %ld\n", type);
-	switch (type)
-	{
-		case 1:		/* \\xxx or \\.xxx */
-		case 6:		/* \\.\xxx */
-			break;
 
-		case 2:
-      break;
-#if 0    
-  /* this makes a direct CreateFileW call crash! */
+/******************************************************************
+ *    get_full_path_helper
+ *
+ * Helper for RtlGetFullPathName_U
+ * Note: name and buffer are allowed to point to the same memory spot
+ */
+static ULONG get_full_path_helper(
+   LPCWSTR name, 
+   LPWSTR buffer, 
+   ULONG size)
+{
+    ULONG                       reqsize = 0, mark = 0, dep = 0, deplen;
+    DOS_PATHNAME_TYPE           type;
+    LPWSTR                      ins_str = NULL;
+    LPCWSTR                     ptr;
+    const UNICODE_STRING*       cd;
+    WCHAR                       tmp[4];
 
-    case 2:   /* x:\xxx  */
-       *DosName = RtlUpcaseUnicodeChar (*DosName);
-       break;
-#endif      
+    /* return error if name only consists of spaces */
+    for (ptr = name; *ptr; ptr++) if (*ptr != ' ') break;
+    if (!*ptr) return 0;
 
-		case 3:		/* x:xxx   */
-			drive = RtlUpcaseUnicodeChar (*DosName);
-			DosName += 2;
-			len     -= 2;
-CHECKPOINT;
-			if (drive == RtlUpcaseUnicodeChar (cd->DosPath.Buffer[0]))
-			{
-CHECKPOINT;
-				memcpy (TempFullPathName, cd->DosPath.Buffer, cd->DosPath.Length);
-				templen = cd->DosPath.Length / sizeof(WCHAR);
-			}
-			else
-			{
-CHECKPOINT;
-				var[0] = L'=';
-				var[1] = drive;
-				var[2] = L':';
-				var[3] = 0;
-				usvar.Length = 3 * sizeof(WCHAR);
-				usvar.MaximumLength = 4 * sizeof(WCHAR);
-				usvar.Buffer = var;
-				pfx.Length = 0;
-				pfx.MaximumLength = MAX_PATH;
-				pfx.Buffer = TempFullPathName;
-				Status = RtlQueryEnvironmentVariable_U (NULL,
-				                                        &usvar,
-				                                        &pfx);
-CHECKPOINT;
-				if (!NT_SUCCESS(Status))
-				{
-CHECKPOINT;
-					if (Status == STATUS_BUFFER_TOO_SMALL)
-						return pfx.Length + len * 2 + 2;
-CHECKPOINT;
-					TempFullPathName[0] = drive;
-					TempFullPathName[1] = L':';
-					TempFullPathName[2] = L'\\';
-					TempFullPathName[3] = 0;
-					templen = 3;
-				}
-				else
-				{
-CHECKPOINT;
-					templen = pfx.Length / sizeof(WCHAR);
-				}
+    RtlAcquirePebLock();
 
-			}
-			break;
+    cd = &((PCURDIR)&NtCurrentTeb()->Peb->ProcessParameters->CurrentDirectoryName)->DosPath;
 
-		case 4:		/* \xxx    */
-			wcsncpy (TempFullPathName, cd->DosPath.Buffer, 2);
-			TempFullPathName[2] = 0;
-			templen = wcslen(TempFullPathName);
-			break;
+    switch (type = RtlDetermineDosPathNameType_U(name))
+    {
+    case UNC_PATH:              /* \\foo   */
+        ptr = skip_unc_prefix( name );
+        mark = (ptr - name);
+        break;
 
-		case 5:		/* xxx     */
-			memcpy (TempFullPathName, cd->DosPath.Buffer, cd->DosPath.Length);
-			templen = cd->DosPath.Length / sizeof(WCHAR);
-			break;
+    case DEVICE_PATH:           /* \\.\foo */
+        mark = 4;
+        break;
 
-		case 7:		/* \\.     */
-			memcpy (TempFullPathName, L"\\\\.\\", 8);
-			templen = 4;
-			break;
+    case ABSOLUTE_DRIVE_PATH:   /* c:\foo  */
+        reqsize = sizeof(WCHAR);
+        tmp[0] = towupper(name[0]);
+        ins_str = tmp;
+        dep = 1;
+        mark = 3;
+        break;
 
-		default:
-			return 0;
-	}
+    case RELATIVE_DRIVE_PATH:   /* c:foo   */
+        dep = 2;
+        if (towupper(name[0]) != towupper(cd->Buffer[0]) || cd->Buffer[1] != ':')
+        {
+            UNICODE_STRING      var, val;
 
+            tmp[0] = '=';
+            tmp[1] = name[0];
+            tmp[2] = ':';
+            tmp[3] = '\0';
+            var.Length = 3 * sizeof(WCHAR);
+            var.MaximumLength = 4 * sizeof(WCHAR);
+            var.Buffer = tmp;
+            val.Length = 0;
+            val.MaximumLength = size;
+            val.Buffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, size);
 
-	RtlReleasePebLock();
+            switch (RtlQueryEnvironmentVariable_U(NULL, &var, &val))
+            {
+            case STATUS_SUCCESS:
+                /* FIXME: Win2k seems to check that the environment variable actually points 
+                 * to an existing directory. If not, root of the drive is used
+                 * (this seems also to be the only spot in RtlGetFullPathName that the 
+                 * existence of a part of a path is checked)
+                 */
+                /* fall thru */
+            case STATUS_BUFFER_TOO_SMALL:
+                reqsize = val.Length + sizeof(WCHAR); /* append trailing '\\' */
+                val.Buffer[val.Length / sizeof(WCHAR)] = '\\';
+                ins_str = val.Buffer;
+                break;
+            case STATUS_VARIABLE_NOT_FOUND:
+                reqsize = 3 * sizeof(WCHAR);
+                tmp[0] = name[0];
+                tmp[1] = ':';
+                tmp[2] = '\\';
+                ins_str = tmp;
+                break;
+            default:
+                DPRINT1("Unsupported status code\n");
+                break;
+            }
+            mark = 3;
+            break;
+        }
+        /* fall through */
 
-	DPRINT("TempFullPathName \'%S\' DosName \'%S\' len %ld\n", TempFullPathName, DosName, len);
-	/* add dosname to prefix */
-	memcpy (TempFullPathName + templen, DosName, len * sizeof(WCHAR));
-  
-  /* dirty/temporary fix for the CreateFileW problem */
-  if (type == 2){
-    TempFullPathName[0] = RtlUpcaseUnicodeChar(TempFullPathName[0]);
-  }
-	len += templen;
-	TempFullPathName[len] = 0;
+    case RELATIVE_PATH:         /* foo     */
+        reqsize = cd->Length;
+        ins_str = cd->Buffer;
+        if (cd->Buffer[1] != ':')
+        {
+            ptr = skip_unc_prefix( cd->Buffer );
+            mark = ptr - cd->Buffer;
+        }
+        else mark = 3;
+        break;
 
-	CHECKPOINT;
-	/* replace slashes */
-	wcs = wcschr(TempFullPathName, L'/');
-	while(wcs)
-	{
-	    *wcs = L'\\';
-	    wcs = wcschr(wcs + 1, L'/');
-	}
+    case ABSOLUTE_PATH:         /* \xxx    */
+#ifdef __WINE__
+        if (name[0] == '/')  /* may be a Unix path */
+        {
+            const WCHAR *ptr = name;
+            int drive = find_drive_root( &ptr );
+            if (drive != -1)
+            {
+                reqsize = 3 * sizeof(WCHAR);
+                tmp[0] = 'A' + drive;
+                tmp[1] = ':';
+                tmp[2] = '\\';
+                ins_str = tmp;
+                mark = 3;
+                dep = ptr - name;
+                break;
+            }
+        }
+#endif
+        if (cd->Buffer[1] == ':')
+        {
+            reqsize = 2 * sizeof(WCHAR);
+            tmp[0] = cd->Buffer[0];
+            tmp[1] = ':';
+            ins_str = tmp;
+            mark = 3;
+        }
+        else
+        {
+            ptr = skip_unc_prefix( cd->Buffer );
+            reqsize = (ptr - cd->Buffer) * sizeof(WCHAR);
+            mark = reqsize / sizeof(WCHAR);
+            ins_str = cd->Buffer;
+        }
+        break;
 
-	if (len == 2 && TempFullPathName[1] == L':')
-	{
-		TempFullPathName[len++] = L'\\';
-		TempFullPathName[len] = 0;
-	}
+    case UNC_DOT_PATH:         /* \\.     */
+        reqsize = 4 * sizeof(WCHAR);
+        dep = 3;
+        tmp[0] = '\\';
+        tmp[1] = '\\';
+        tmp[2] = '.';
+        tmp[3] = '\\';
+        ins_str = tmp;
+        mark = 4;
+        break;
 
+    case INVALID_PATH:
+        goto done;
+    }
 
-	DPRINT("TempFullPathName \'%S\'\n", TempFullPathName);
-	RtlpEatPath (TempFullPathName);
-	DPRINT("TempFullPathName \'%S\'\n", TempFullPathName);
+    /* enough space ? */
+    deplen = wcslen(name + dep) * sizeof(WCHAR);
+    if (reqsize + deplen + sizeof(WCHAR) > size)
+    {
+        /* not enough space, return need size (including terminating '\0') */
+        reqsize += deplen + sizeof(WCHAR);
+        goto done;
+    }
 
-	len = wcslen (TempFullPathName);
+    memmove(buffer + reqsize / sizeof(WCHAR), name + dep, deplen + sizeof(WCHAR));
+    if (reqsize) memcpy(buffer, ins_str, reqsize);
+    reqsize += deplen;
 
-	if (len < (size / sizeof(WCHAR)))
-	{
-		if (buf)
-			memcpy (buf, TempFullPathName, (len + 1) * sizeof(WCHAR));
+    if (ins_str && ins_str != tmp && ins_str != cd->Buffer)
+        RtlFreeHeap(RtlGetProcessHeap(), 0, ins_str);
 
-		/* find file part */
-		if (FilePart && buf)
-		{
-#if 0
-			*FilePart = wcsrchr(buf, L'\\');
-			if (*FilePart)
-			{
-			    (*FilePart)++;
-			}
-			else
-			{
-			    *FilePart = buf;
-			}
-#else
-			*FilePart = buf + len;
-			while (*FilePart != buf && **FilePart != L'\\')
-                            --(*FilePart);
-                        if (**FilePart == L'\\')
-                            ++(*FilePart);
-#endif
-		}
+    collapse_path( buffer, mark );
+    reqsize = wcslen(buffer) * sizeof(WCHAR);
+
+done:
+    RtlReleasePebLock();
+    return reqsize;
+}
+
+
+/******************************************************************
+ *    RtlGetFullPathName_U  (NTDLL.@)
+ *
+ * Returns the number of bytes written to buffer (not including the
+ * terminating NULL) if the function succeeds, or the required number of bytes
+ * (including the terminating NULL) if the buffer is too small.
+ *
+ * file_part will point to the filename part inside buffer (except if we use
+ * DOS device name, in which case file_in_buf is NULL)
+ *
+ * @implemented
+ */
+DWORD STDCALL RtlGetFullPathName_U(
+   const WCHAR* name, 
+   ULONG size, 
+   WCHAR* buffer,
+   WCHAR** file_part)
+{
+    WCHAR*      ptr;
+    DWORD       dosdev;
+    DWORD       reqsize;
+
+    DPRINT("RtlGetFullPathName_U(%S %lu %p %p)\n", name, size, buffer, file_part);
+
+    if (!name || !*name) return 0;
+
+    if (file_part) *file_part = NULL;
+
+    /* check for DOS device name */
+    dosdev = RtlIsDosDeviceName_U((WCHAR*)name);
+    if (dosdev)
+    {
+        DWORD   offset = HIWORD(dosdev) / sizeof(WCHAR); /* get it in WCHARs, not bytes */
+        DWORD   sz = LOWORD(dosdev); /* in bytes */
+
+        if (8 + sz + 2 > size) return sz + 10;
+        wcscpy(buffer, DeviceRootW);
+        memmove(buffer + 4, name + offset, sz);
+        buffer[4 + sz / sizeof(WCHAR)] = '\0';
+        /* file_part isn't set in this case */
+        return sz + 8;
+    }
+
+    reqsize = get_full_path_helper(name, buffer, size);
+    if (!reqsize) return 0;
+    if (reqsize > size)
+    {
+        LPWSTR tmp = RtlAllocateHeap(RtlGetProcessHeap(), 0, reqsize);
+        reqsize = get_full_path_helper(name, tmp, reqsize);
+        if (reqsize > size)  /* it may have worked the second time */
+        {
+            RtlFreeHeap(RtlGetProcessHeap(), 0, tmp);
+            return reqsize + sizeof(WCHAR);
         }
+        memcpy( buffer, tmp, reqsize + sizeof(WCHAR) );
+        RtlFreeHeap(RtlGetProcessHeap(), 0, tmp);
+    }
 
-	return len * sizeof(WCHAR);
+    /* find file part */
+    if (file_part && (ptr = wcsrchr(buffer, '\\')) != NULL && ptr >= buffer + 2 && *++ptr)
+        *file_part = ptr;
+    return reqsize;
 }