Index: /trunk/include/iprt/path.h
===================================================================
--- /trunk/include/iprt/path.h	(revision 19925)
+++ /trunk/include/iprt/path.h	(revision 19926)
@@ -299,5 +299,5 @@
  * such as:
  * <ul>
- * <li>On DOS-like platforms, both |\| and |/| separator chars are considered
+ * <li>On DOS-like platforms, both separator chars (|\| and |/|) are considered
  *     to be equal.
  * <li>On platforms with case-insensitive file systems, mismatching characters
@@ -305,15 +305,16 @@
  * </ul>
  *
- * File system details are currently ignored. This means that you won't get
- * case-insensitive compares on unix systems when a path goes into a case-insensitive
- * filesystem like FAT, HPFS, HFS, NTFS, JFS, or similar. For NT, OS/2 and similar
- * you'll won't get case-sensitive compares on a case-sensitive file system.
- *
- * @param   pszPath1    Path to compare (must be an absolute path).
- * @param   pszPath2    Path to compare (must be an absolute path).
- *
  * @returns @< 0 if the first path less than the second path.
  * @returns 0 if the first path identical to the second path.
  * @returns @> 0 if the first path greater than the second path.
+ *
+ * @param   pszPath1    Path to compare (must be an absolute path).
+ * @param   pszPath2    Path to compare (must be an absolute path).
+ *
+ * @remarks File system details are currently ignored. This means that you won't
+ *          get case-insentive compares on unix systems when a path goes into a
+ *          case-insensitive filesystem like FAT, HPFS, HFS, NTFS, JFS, or
+ *          similar. For NT, OS/2 and similar you'll won't get case-sensitve
+ *          compares on a case-sensitive file system.
  */
 RTDECL(int) RTPathCompare(const char *pszPath1, const char *pszPath2);
@@ -322,10 +323,13 @@
  * Checks if a path starts with the given parent path.
  *
- * This means that either the path and the parent path matches completely, or that
- * the path is to some file or directory residing in the tree given by the parent
- * directory.
+ * This means that either the path and the parent path matches completely, or
+ * that the path is to some file or directory residing in the tree given by the
+ * parent directory.
  *
  * The path comparison takes platform-dependent details into account,
  * see RTPathCompare() for details.
+ *
+ * @returns |true| when \a pszPath starts with \a pszParentPath (or when they
+ *          are identical), or |false| otherwise.
  *
  * @param   pszPath         Path to check, must be an absolute path.
@@ -333,12 +337,34 @@
  *                          No trailing directory slash!
  *
- * @returns |true| when \a pszPath starts with \a pszParentPath (or when they
- *          are identical), or |false| otherwise.
- *
- * @remark  This API doesn't currently handle root directory compares in a manner
- *          consistent with the other APIs. RTPathStartsWith(pszSomePath, "/") will
- *          not work if pszSomePath isn't "/".
+ * @remarks This API doesn't currently handle root directory compares in a
+ *          manner consistant with the other APIs. RTPathStartsWith(pszSomePath,
+ *          "/") will not work if pszSomePath isn't "/".
  */
 RTDECL(bool) RTPathStartsWith(const char *pszPath, const char *pszParentPath);
+
+/**
+ * Appends one partial path to another.
+ *
+ * The main purpose of this function is to deal correctly with leading and
+ * trailing slashes.
+ *
+ * @returns IPRT status code.
+ * @retval  VERR_PATH
+ *
+ * @param   pszPath         The path to append pszAppend to. This serves as both
+ *                          input and output. This can be empty, in which case
+ *                          pszAppend is just copied over.
+ * @param   cchPathDst      The size of the buffer pszPath points to. This
+ *                          should NOT be strlen(pszPath).
+ * @param   pszAppend       The partial path to append to pszPath. This can be
+ *                          NULL, in which case nothing is done.
+ *
+ * @remarks On OS/2, Window and similar systems, concatenating a drive letter
+ *          specifier with a root prefixed path will result in an absolute path.
+ *          Meaning, RTPathAppend(strcpy(szBuf, "C:"), sizeof(szBuf), "/bar")
+ *          will result in "C:/bar". (This follows directly from the behavior
+ *          when pszPath is empty.)
+ */
+RTDECL(int) RTPathAppend(char *pszPath, size_t cchPathDst, const char *pszAppend);
 
 
Index: /trunk/src/VBox/Runtime/r3/path.cpp
===================================================================
--- /trunk/src/VBox/Runtime/r3/path.cpp	(revision 19925)
+++ /trunk/src/VBox/Runtime/r3/path.cpp	(revision 19926)
@@ -448,5 +448,5 @@
  * such as:
  * <ul>
- * <li>On DOS-like platforms, both |\| and |/| separator chars are considered
+ * <li>On DOS-like platforms, both separator chars (|\| and |/|) are considered
  *     to be equal.
  * <li>On platforms with case-insensitive file systems, mismatching characters
@@ -454,17 +454,16 @@
  * </ul>
  *
- * @remark
- *
- * File system details are currently ignored. This means that you won't get
- * case-insentive compares on unix systems when a path goes into a case-insensitive
- * filesystem like FAT, HPFS, HFS, NTFS, JFS, or similar. For NT, OS/2 and similar
- * you'll won't get case-sensitve compares on a case-sensitive file system.
- *
- * @param   pszPath1    Path to compare (must be an absolute path).
- * @param   pszPath2    Path to compare (must be an absolute path).
- *
  * @returns @< 0 if the first path less than the second path.
  * @returns 0 if the first path identical to the second path.
  * @returns @> 0 if the first path greater than the second path.
+ *
+ * @param   pszPath1    Path to compare (must be an absolute path).
+ * @param   pszPath2    Path to compare (must be an absolute path).
+ *
+ * @remarks File system details are currently ignored. This means that you won't
+ *          get case-insentive compares on unix systems when a path goes into a
+ *          case-insensitive filesystem like FAT, HPFS, HFS, NTFS, JFS, or
+ *          similar. For NT, OS/2 and similar you'll won't get case-sensitve
+ *          compares on a case-sensitive file system.
  */
 RTDECL(int) RTPathCompare(const char *pszPath1, const char *pszPath2)
@@ -477,10 +476,13 @@
  * Checks if a path starts with the given parent path.
  *
- * This means that either the path and the parent path matches completely, or that
- * the path is to some file or directory residing in the tree given by the parent
- * directory.
+ * This means that either the path and the parent path matches completely, or
+ * that the path is to some file or directory residing in the tree given by the
+ * parent directory.
  *
  * The path comparison takes platform-dependent details into account,
  * see RTPathCompare() for details.
+ *
+ * @returns |true| when \a pszPath starts with \a pszParentPath (or when they
+ *          are identical), or |false| otherwise.
  *
  * @param   pszPath         Path to check, must be an absolute path.
@@ -488,10 +490,7 @@
  *                          No trailing directory slash!
  *
- * @returns |true| when \a pszPath starts with \a pszParentPath (or when they
- *          are identical), or |false| otherwise.
- *
- * @remark  This API doesn't currently handle root directory compares in a manner
- *          consistant with the other APIs. RTPathStartsWith(pszSomePath, "/") will
- *          not work if pszSomePath isn't "/".
+ * @remarks This API doesn't currently handle root directory compares in a
+ *          manner consistant with the other APIs. RTPathStartsWith(pszSomePath,
+ *          "/") will not work if pszSomePath isn't "/".
  */
 RTDECL(bool) RTPathStartsWith(const char *pszPath, const char *pszParentPath)
@@ -542,4 +541,70 @@
         return RTStrDup(szPath);
     return NULL;
+}
+
+
+/**
+ * Figures the length of the root part of the path.
+ *
+ * @returns length of the root specifier.
+ * @retval  0 if none.
+ *
+ * @param   pszPath         The path to investigate.
+ *
+ * @remarks Unnecessary root slashes will not be counted. The caller will have
+ *          to deal with it where it matters.
+ */
+static size_t rtPathRootSpecLen(const char *pszPath)
+{
+    /* fend of wildlife. */
+    if (!pszPath)
+        return 0;
+
+    /* Root slash? */
+    if (RTPATH_IS_SLASH(pszPath[0]))
+    {
+#if defined (RT_OS_OS2) || defined (RT_OS_WINDOWS)
+        /* UNC? */
+        if (    RTPATH_IS_SLASH(pszPath[1])
+            &&  pszPath[2] != '\0'
+            &&  !RTPATH_IS_SLASH(pszPath[2]))
+        {
+            /* Find the end of the server name. */
+            const char *pszEnd = pszPath + 2;
+            pszEnd += 2;
+            while (   *pszEnd != '\0'
+                   && !RTPATH_IS_SLASH(*pszEnd))
+                pszEnd++;
+            if (RTPATH_IS_SLASH(*pszEnd))
+            {
+                pszEnd++;
+                while (RTPATH_IS_SLASH(*pszEnd))
+                    pszEnd++;
+
+                /* Find the end of the share name */
+                while (   *pszEnd != '\0'
+                       && !RTPATH_IS_SLASH(*pszEnd))
+                    pszEnd++;
+                if (RTPATH_IS_SLASH(*pszEnd))
+                    pszEnd++;
+                return pszPath - pszEnd;
+            }
+        }
+#endif
+        return 1;
+    }
+
+#if defined (RT_OS_OS2) || defined (RT_OS_WINDOWS)
+    /* Drive specifier? */
+    if (   pszPath[0] != '\0'
+        && pszPath[1] == ':'
+        && RT_C_IS_ALPHA(pszPath[0]))
+    {
+        if (RTPATH_IS_SLASH(pszPath[2]))
+            return 3;
+        return 2;
+    }
+#endif
+    return 0;
 }
 
@@ -664,4 +729,83 @@
 
 
+RTDECL(int) RTPathAppend(char *pszPath, size_t cchPath, const char *pszAppend)
+{
+    char *pszPathEnd = (char *)memchr(pszPath, '\0', cchPath);
+    AssertReturn(pszPathEnd, VERR_INVALID_PARAMETER);
+
+    /*
+     * Special cases.
+     */
+    if (!pszAppend)
+        return VINF_SUCCESS;
+    size_t cchAppend = strlen(pszAppend);
+    if (!cchAppend)
+        return VINF_SUCCESS;
+    if (pszPathEnd == pszPath)
+    {
+        if (cchAppend >= cchPath)
+            return VERR_BUFFER_OVERFLOW;
+        memcpy(pszPath, pszAppend, cchAppend + 1);
+        return VINF_SUCCESS;
+    }
+
+    /*
+     * Balance slashes and check for buffer overflow.
+     */
+    bool fAddSlash = false;
+    if (!RTPATH_IS_SLASH(pszPathEnd[-1]))
+    {
+        if (!RTPATH_IS_SLASH(pszAppend[0]))
+        {
+#if defined (RT_OS_OS2) || defined (RT_OS_WINDOWS)
+            if (    pszPath[1] == ':'
+                &&  RT_C_IS_ALPHA(pszPath[0]))
+            {
+                if ((size_t)(pszPathEnd - pszPath) + cchAppend >= cchPath)
+                    return VERR_BUFFER_OVERFLOW;
+            }
+            else
+#endif
+            {
+                if ((size_t)(pszPathEnd - pszPath) + 1 + cchAppend >= cchPath)
+                    return VERR_BUFFER_OVERFLOW;
+                *pszPathEnd++ = '/';
+            }
+        }
+        else
+        {
+            /* One slash is sufficient at this point. */
+            while (RTPATH_IS_SLASH(pszAppend[1]))
+                pszAppend++, cchAppend--;
+
+            if ((size_t)(pszPathEnd - pszPath) + cchAppend >= cchPath)
+                return VERR_BUFFER_OVERFLOW;
+        }
+    }
+    else
+    {
+        /* No slashes needed in the appended bit. */
+        while (RTPATH_IS_SLASH(*pszAppend))
+            pszAppend++, cchAppend--;
+
+        /* In the leading path we can skip unnecessary trailing slashes, but
+           be sure to leave one. */
+        size_t const cchRoot = rtPathRootSpecLen(pszPath);
+        while (     (size_t)(pszPathEnd - pszPath) > RT_MAX(1, cchRoot)
+               &&   RTPATH_IS_SLASH(pszPathEnd[-2]))
+            pszPathEnd--;
+
+        if ((size_t)(pszPathEnd - pszPath) + cchAppend >= cchPath)
+            return VERR_BUFFER_OVERFLOW;
+    }
+
+    /*
+     * What remains now is the just the copying.
+     */
+    memcpy(pszPathEnd, pszAppend, cchAppend + 1);
+    return VINF_SUCCESS;
+}
+
+
 #ifndef RT_MINI
 
Index: /trunk/src/VBox/Runtime/testcase/tstPath.cpp
===================================================================
--- /trunk/src/VBox/Runtime/testcase/tstPath.cpp	(revision 19925)
+++ /trunk/src/VBox/Runtime/testcase/tstPath.cpp	(revision 19926)
@@ -45,4 +45,6 @@
 int main()
 {
+    char szPath[RTPATH_MAX];
+
     /*
      * Init RT+Test.
@@ -62,5 +64,4 @@
      */
     RTTestSub(hTest, "RTPathExecDir");
-    char szPath[RTPATH_MAX];
     RTTESTI_CHECK_RC(RTPathExecDir(szPath, sizeof(szPath)), VINF_SUCCESS);
     if (RT_SUCCESS(rc))
@@ -232,5 +233,4 @@
         const char *pszInput  = s_apszStripFilenameTests[i];
         const char *pszExpect = s_apszStripFilenameTests[i + 1];
-        char szPath[RTPATH_MAX];
         strcpy(szPath, pszInput);
         RTPathStripFilename(szPath);
@@ -248,4 +248,77 @@
      * RTPathAppend.
      */
+    RTTestSub(hTest, "RTPathAppend");
+    static const char *s_apszAppendTests[] =
+    {
+        /* base                 append                  result */
+        "/",                    "",                     "/",
+        "",                     "/",                    "/",
+        "/",                    "/",                    "/",
+        "/x",                   "",                     "/x",
+        "/x",                   "/",                    "/x/",
+        "/",                    "x",                    "/x",
+        "dir",                  "file",                 "dir/file",
+        "dir",                  "/file",                "dir/file",
+        "dir",                  "//file",               "dir/file",
+        "dir",                  "///file",              "dir/file",
+        "dir/",                 "/file",                "dir/file",
+        "dir/",                 "//file",               "dir/file",
+        "dir/",                 "///file",              "dir/file",
+        "dir//",                "file",                 "dir/file",
+        "dir//",                "/file",                "dir/file",
+        "dir//",                "//file",               "dir/file",
+        "dir///",               "///file",              "dir/file",
+#if defined (RT_OS_OS2) || defined (RT_OS_WINDOWS)
+        "/",                    "\\",                   "/",
+        "\\",                   "/",                    "\\",
+        "\\\\srv\\shr",         "dir//",                "\\\\srv\\shr/dir//",
+        "\\\\srv\\shr",         "dir//file",            "\\\\srv\\shr/dir//file",
+        "\\\\srv\\shr",         "//dir//",              "\\\\srv\\shr/dir//",
+        "\\\\srv\\shr",         "/\\dir//",             "\\\\srv\\shr\\dir//",
+        "\\\\",                 "not-srv/not-shr/file", "\\not-srv/not-shr/file",
+        "C:",                   "autoexec.bat",         "C:autoexec.bat",
+        "C:",                   "/autoexec.bat",        "C:/autoexec.bat",
+        "C:",                   "\\autoexec.bat",       "C:\\autoexec.bat",
+        "C:\\",                 "/autoexec.bat",        "C:\\autoexec.bat",
+        "C:\\\\",               "autoexec.bat",         "C:\\autoexec.bat",
+#endif
+    };
+    for (unsigned i = 0; i < RT_ELEMENTS(s_apszAppendTests); i += 3)
+    {
+        const char *pszInput  = s_apszAppendTests[i];
+        const char *pszAppend = s_apszAppendTests[i + 1];
+        const char *pszExpect = s_apszAppendTests[i + 2];
+        strcpy(szPath, pszInput);
+        RTTESTI_CHECK_RC(rc = RTPathAppend(szPath, sizeof(szPath), pszAppend), VINF_SUCCESS);
+        if (RT_FAILURE(rc))
+            continue;
+        if (strcmp(szPath, pszExpect))
+        {
+            RTTestIFailed("Unexpected result\n"
+                          "   input: '%s'\n"
+                          "  append: '%s'\n"
+                          "  output: '%s'\n"
+                          "expected: '%s'",
+                          pszInput, pszAppend, szPath, pszExpect);
+        }
+        else
+        {
+            size_t const cchResult = strlen(szPath);
+
+            strcpy(szPath, pszInput);
+            RTTESTI_CHECK_RC(rc = RTPathAppend(szPath, cchResult + 2, pszAppend), VINF_SUCCESS);
+            RTTESTI_CHECK(RT_FAILURE(rc) || !strcmp(szPath, pszExpect));
+
+            strcpy(szPath, pszInput);
+            RTTESTI_CHECK_RC(rc = RTPathAppend(szPath, cchResult + 1, pszAppend), VINF_SUCCESS);
+            RTTESTI_CHECK(RT_FAILURE(rc) || !strcmp(szPath, pszExpect));
+
+            if (strlen(pszInput) < cchResult)
+            {
+                strcpy(szPath, pszInput);
+                RTTESTI_CHECK_RC(RTPathAppend(szPath, cchResult, pszAppend), VERR_BUFFER_OVERFLOW);
+            }
+        }
+    }
 
 
