Index: /trunk/libc/src/libc/misc/abspath.c
===================================================================
--- /trunk/libc/src/libc/misc/abspath.c	(revision 3924)
+++ /trunk/libc/src/libc/misc/abspath.c	(revision 3925)
@@ -1,129 +1,527 @@
-/* abspath.c (emx+gcc) -- Copyright (c) 1992-1998 by Eberhard Mattes */
-
+/* $Id$ */
+/** @file
+ * kLibC - Implementation of _abspath().
+ *
+ * @copyright   Copyright (C) 2014 knut st. osmundsen <bird-klibc-spam-xiv@anduin.net>
+ * @licenses    MIT, BSD2, BSD3, BSD4, LGPLv2.1, LGPLv3, LGPLvFuture.
+ */
+
+
+/*******************************************************************************
+*   Header Files                                                               *
+*******************************************************************************/
 #include "libc-alias.h"
 #include <stdlib.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
 #include <string.h>
 #include <alloca.h>
-#include <errno.h>
+#include <sys/cdefs.h>
 #include <sys/param.h>
-#include <emx/syscalls.h>
 #include <InnoTekLIBC/libc.h>
 #include <InnoTekLIBC/locale.h>
 #include <InnoTekLIBC/backend.h>
-
-#define FALSE   0
-#define TRUE    1
-#define IS_PATH_DELIM(c) ((c)=='\\' || (c)=='/')
-
-int _abspath (char *dst, const char *src, int size)
+#ifdef __OS2__
+# include <InnoTekLIBC/pathrewrite.h>
+extern int  __libc_gcchUnixRoot; /* fs.c  / b_fs.h */
+#endif
+
+
+/** Macro that advance the a_pszSrc variable past all leading slashes. */
+#define _ABSPATH_SKIP_SLASHES(a_pszSrc) \
+    do { \
+        char ch = *(a_pszSrc); \
+        while (__KLIBC_PATH_IS_SLASH(ch)) \
+            ch = *++(a_pszSrc); \
+    } while (0)
+
+
+/**
+ * Sets errno to ERANGE and returns -1.
+ *
+ * Convenience for dropping three lines of code for each overflow check and
+ * return.
+ *
+ * @returns -1
+ */
+static int _abspath_overflow(void)
 {
-  char drive, dir[MAXPATHLEN+1], src_drive, *s, *p;
-  int i, j, rel, server, trail, mbcl;
-  char chSlash = '/';
-
-  s = alloca (strlen (src) + 1);
-  strcpy (s, src);
-  src_drive = _fngetdrive (s);
-  if (src_drive == 0)
-    drive = _getdrive ();
-  else
-    {
-      drive = src_drive;
-      s += 2;
-    }
-  dir[0] = 0; rel = FALSE; server = FALSE; trail = FALSE;
-  if (IS_PATH_DELIM (*s))
-    {
-      ++s;
-      if (IS_PATH_DELIM (*s))
-        {
-          ++s; server = TRUE;
-        }
-    }
-  else if (__libc_Back_fsDirCurrentGet (dir, sizeof(dir), src_drive, __LIBC_BACK_FSCWD_NO_DRIVE | __LIBC_BACK_FSCWD_NO_ROOT_SLASH) == 0)
-    _fnslashify (dir);
-  else
-    {
-      dir[0] = 0;
-      rel = TRUE;
-    }
-
-  while (*s != 0)
-    {
-      if (s[0] == '.' && (IS_PATH_DELIM (s[1]) || s[1] == 0))
-        ++s;
-      else if (s[0] == '.' && s[1] == '.' && (IS_PATH_DELIM (s[2]) ||
-                                              s[2] == 0))
-        {
-          s += 2;
-          if (*dir == 0)
-            strcpy (dir, "..");
-          else
-            {
-              p = _getname (dir);
-              if (p == dir)
-                p[0] = 0;
-              else
-                p[-1] = 0;
-            }
-        }
-      else
-        {
-          i = strlen (dir);
-          if (i < sizeof (dir) - 1)
-            dir[i++] = chSlash;
-          while (*s != 0)
-            {
-              if (CHK_MBCS_PREFIX (&__libc_GLocaleCtype, *s, mbcl) && s[1] != 0)
+    errno = ERANGE;
+    return -1;
+}
+
+
+/**
+ * Figures out the length of the directory component @a pszSrc points to.
+ *
+ * @returns Length in bytes.
+ * @param   pszSrc      Pointer to the path component which length we're
+ *                      interested in.  This should not point to a slash but may
+ *                      point to the zero terminator character.
+ */
+static int _abspath_component_length(const char *pszSrc)
+{
+    char const *psz = pszSrc;
+    char        ch;
+    assert(!__KLIBC_PATH_IS_SLASH(*psz));
+
+    if (__libc_GLocaleCtype.mbcs)
+    {
+        /* May contain multibyte chars. */
+        int cchMultibyteCodepoint;
+        while ((ch = *psz) != '\0')
+        {
+            if (   CHK_MBCS_PREFIX(&__libc_GLocaleCtype, ch, cchMultibyteCodepoint)
+                && psz[1] != '\0')
+                psz += cchMultibyteCodepoint;
+            else if (__KLIBC_PATH_IS_SLASH(ch))
+                break;
+            else
+                psz++;
+        }
+    }
+    else
+    {
+        /* All chars are single byte. */
+        while ((ch = *psz) != '\0' && !__KLIBC_PATH_IS_SLASH(ch))
+            psz++;
+    }
+    return (int)(psz - pszSrc);
+}
+
+
+/**
+ * Fixes the slashes of a current directory we got from the backend.
+ *
+ * @returns Length of the current returned directory.
+ * @param   pszDst      The destination buffer which slashes we should fix.
+ * @param   chSlash     The prefereed slash.
+ */
+static int _abspath_slashify(char *pszDst, char chSlash)
+{
+#ifdef __KLIBC_PATH_SLASH_ALT
+    char *psz = pszDst;
+    char  ch;
+    if (__libc_GLocaleCtype.mbcs)
+    {
+        /* May contain multibyte chars. */
+        int cchMultibyteCodepoint;
+        while ((ch = *psz) != '\0')
+        {
+            if (   CHK_MBCS_PREFIX(&__libc_GLocaleCtype, ch, cchMultibyteCodepoint)
+                && psz[1] != '\0')
+                psz += cchMultibyteCodepoint;
+            else if (__KLIBC_PATH_IS_SLASH(ch))
+                *psz++ = chSlash;
+            else
+                psz++;
+        }
+    }
+    else
+    {
+        /* All chars are single byte. */
+        while ((ch = *psz) != '\0')
+        {
+            if (__KLIBC_PATH_IS_SLASH(ch))
+                *psz = chSlash;
+            psz++;
+        }
+    }
+    return (int)(psz - pszDst);
+#else
+    return strlen(pszDst);
+#endif
+}
+
+
+#ifdef __OS2__
+/**
+ * Checks if @a pszSrc points something that could be an OS/2 device name.
+ *
+ * @returns 1 if likely OS/2 device name, 0 if not.
+ * @param   pszSrc          Pointer to the char after "/DEV/".
+ */
+static int _abspath_is_likely_device_name(const char *pszSrc)
+{
+    _ABSPATH_SKIP_SLASHES(pszSrc);
+    size_t cch = _abspath_component_length(pszSrc);
+    if (cch > 0 && cch <= 8 && !pszSrc[cch])
+    {
+        if (cch == 1)
+            return pszSrc[0] != '.';
+        if (cch == 2)
+            return pszSrc[0] != '.' || pszSrc[1] != '.';
+        return 1;
+    }
+    return 0;
+}
+#endif
+
+int _abspath(char *pszDst, const char *pszSrc, int cbDst)
+{
+    /*
+     * Note! This code must mimick fsResolveUnix and fsResolveOS2
+     */
+#ifdef __KLIBC_PATH_SLASH_ALT
+    char const chSlash = !__libc_gfNoUnix ? __KLIBC_PATH_SLASH_ALT : __KLIBC_PATH_SLASH;
+#else
+    char const chSlash = __KLIBC_PATH_SLASH;
+#endif
+#ifdef __KLIBC_PATH_HAVE_DRIVE_LETTERS
+    char chDrive;
+#endif
+
+    /*
+     * Make a copy of the input if it overlaps the destination buffer
+     * OR if the
+     */
+    size_t cchSrc = strlen(pszSrc);
+    if (   (uintptr_t)pszDst <= (uintptr_t)pszSrc + cchSrc
+        && (uintptr_t)pszDst + (uintptr_t)cbDst > (uintptr_t)pszSrc)
+    {
+        char *pszSrcCopy = (char *)alloca(cchSrc + 1);
+        memcpy(pszSrcCopy, pszSrc, cchSrc + 1);
+        pszSrc = pszSrcCopy;
+    }
+
+    /*
+     * Deal with the start of the path. This will skip
+     */
+    int cch;
+    int offDst     = 0;
+#define _ABSPATH_ERANGE_RETURN_CHECK(a_cbNeeded) if (cbDst <= (a_cbNeeded)) return _abspath_overflow(); else do {} while (0)
+    int offDstRoot = 0;
+    /* Root slash? */
+    if (__KLIBC_PATH_IS_SLASH(*pszSrc))
+    {
+#ifdef __KLIBC_PATH_HAVE_UNC
+        /*
+         * Check for and deal with UNC.
+         * Note! We do not allow '..' to advance higher than the share name,
+         *       i.e. "//server/share/.." becomes "//server/share/".
+         */
+        /** @todo Should "//server/share/.." become "//server/share" rather than "//server/share/" ?? */
+        if (   __KLIBC_PATH_IS_SLASH(pszSrc[1])
+            && !__KLIBC_PATH_IS_SLASH(pszSrc[2])
+            && pszSrc[2])
+        {
+            /* The server part. */
+            cch = _abspath_component_length(&pszSrc[2]);
+            _ABSPATH_ERANGE_RETURN_CHECK(2 + cch);
+            pszDst[0] = chSlash;
+            pszDst[1] = chSlash;
+            memcpy(&pszDst[2], &pszSrc[2], cch);
+            offDst = 2 + cch;
+            pszSrc += 2 + cch;
+
+            /* The share/resource too, if present. */
+            if (*pszSrc)
+            {
+                pszSrc++;
+                _ABSPATH_SKIP_SLASHES(pszSrc);
+                cch = _abspath_component_length(pszSrc);
+                _ABSPATH_ERANGE_RETURN_CHECK(offDst + 1 + cch);
+                pszDst[offDst++] = chSlash;
+                memcpy(&pszDst[offDst], pszSrc, cch);
+                offDst += cch;
+                pszSrc += cch;
+                if (*pszSrc)
                 {
-                  if (i < sizeof (dir) - mbcl)
-                    {
-                      memcpy (dir + i, s, mbcl);
-                      i += mbcl;
-                    }
-                  s += mbcl;
+                    _ABSPATH_ERANGE_RETURN_CHECK(offDst + 1);
+                    pszDst[offDst++] = chSlash;
+                    pszSrc++;
+                    _ABSPATH_SKIP_SLASHES(pszSrc);
                 }
-              else if (IS_PATH_DELIM (*s))
+            }
+            offDstRoot = offDst;
+        }
+        else
+#endif
+        {
+            /* Skip leading slashes before continuing. */
+            pszSrc++;
+            _ABSPATH_SKIP_SLASHES(pszSrc);
+
+            if (0)
+            { /* nothing */ }
+#ifdef __OS2__
+            /*
+             * The \pipe\ path is special on OS/2, all named pipes are invisibly
+             * living under it.  May include subdirectories.  Unsure how '..' is
+             * handled, but assuming the caller wants to straighten those out.
+             */
+            else if (   (pszSrc[0] == 'p' || pszSrc[0] == 'P')
+                     && (pszSrc[1] == 'i' || pszSrc[1] == 'I')
+                     && (pszSrc[2] == 'p' || pszSrc[2] == 'P')
+                     && (pszSrc[3] == 'e' || pszSrc[3] == 'E')
+                     && __KLIBC_PATH_IS_SLASH(pszSrc[4]) )
+            {
+                _ABSPATH_ERANGE_RETURN_CHECK(6);
+                pszDst[0] = chSlash;
+                pszDst[1] = 'P';
+                pszDst[2] = 'I';
+                pszDst[3] = 'P';
+                pszDst[4] = 'E';
+                pszDst[5] = chSlash;
+                offDst = 6;
+                pszSrc += 5;
+                _ABSPATH_SKIP_SLASHES(pszSrc);
+            }
+            /*
+             * The \dev\ path is also special on OS/2, all devices are visibly
+             * (via stat but not readdir) living under it.  Names are limited
+             * to 8 chars and there should not be any subdirs AFAIK.  Since we
+             * may misidentify kNIX pseudo device path here, we do not
+             * uppercase the prefix like we did for the pipes.
+             */
+            else if (   (pszSrc[0] == 'd' || pszSrc[0] == 'D')
+                     && (pszSrc[1] == 'e' || pszSrc[1] == 'E')
+                     && (pszSrc[2] == 'v' || pszSrc[2] == 'V')
+                     && __KLIBC_PATH_IS_SLASH(pszSrc[3])
+                     && _abspath_is_likely_device_name(&pszSrc[4]))
+            {
+                _ABSPATH_ERANGE_RETURN_CHECK(5);
+                pszDst[0] = chSlash;
+                pszDst[1] = pszSrc[0];
+                pszDst[2] = pszSrc[1];
+                pszDst[3] = pszSrc[2];
+                pszDst[4] = chSlash;
+                offDst = 5;
+                pszSrc += 4;
+                _ABSPATH_SKIP_SLASHES(pszSrc);
+            }
+            /*
+             * If the path rewriter triggers on the path, we will not prefix
+             * it with the current driver letter.
+             */
+            else if (__libc_PathRewrite(pszSrc - 1, NULL, 0) > 0)
+            {
+                _ABSPATH_ERANGE_RETURN_CHECK(1);
+                pszDst[0] = chSlash;
+                offDst = 1;
+            }
+#endif /* __OS2__ */
+#ifdef __KLIBC_PATH_HAVE_DRIVE_LETTERS
+            /*
+             * Add the current drive letter before the root slash, unless we've
+             * done chroot() or similar that means '/' refers to the unixroot
+             * instead of the root of the current drive.
+             */
+            else if (   __libc_gcchUnixRoot == 0
+                     && (chDrive = _getdrive()) > 0) /** @todo UNC CWD */
+            {
+                _ABSPATH_ERANGE_RETURN_CHECK(3);
+                pszDst[0] = chDrive;
+                pszDst[1] = ':';
+                pszDst[2] = chSlash;
+                offDst = 3;
+            }
+#endif
+            /*
+             * Unix root slash.
+             */
+            else
+            {
+                _ABSPATH_ERANGE_RETURN_CHECK(1);
+                pszDst[0] = chSlash;
+                offDst = 1;
+            }
+            offDstRoot = offDst;
+        }
+    }
+#ifdef __KLIBC_PATH_HAVE_DRIVE_LETTERS
+    /*
+     * Drive letter?
+     */
+    else if (   pszSrc[0] != '\0'
+             && pszSrc[1] == ':'
+             && (chDrive = _fngetdrive(pszSrc)) )
+    {
+        if (__KLIBC_PATH_IS_SLASH(pszSrc[2]))
+        {
+            /*
+             * Absolute path, great :-).
+             */
+            _ABSPATH_ERANGE_RETURN_CHECK(3);
+            pszDst[0] = chDrive;
+            pszDst[1] = ':';
+            pszDst[2] = chSlash;
+            offDstRoot = offDst = 3;
+            pszSrc += 3;
+            _ABSPATH_SKIP_SLASHES(pszSrc);
+        }
+        else
+        {
+            /*
+             * Drive letter relative path.  Try add the current directory for
+             * that drive.  If we cannot get it (drive letter exists or
+             * something, return the relative path.
+             */
+            pszSrc += 2;
+            int rc = __libc_Back_fsDirCurrentGet(pszDst, cbDst, chDrive, 0 /*fFlags*/);
+            if (rc == -ERANGE)
+                return _abspath_overflow();
+            if (rc == 0)
+            {
+                offDst = _abspath_slashify(pszDst, chSlash);
+                if (*pszSrc && pszDst[offDst - 1] != chSlash)
+                {
+                    _ABSPATH_ERANGE_RETURN_CHECK(offDst + 1);
+                    pszDst[offDst++] = chSlash;
+                }
+                offDstRoot = 3;
+            }
+            else
+            {
+                pszDst[0] = chDrive;
+                pszDst[1] = ':';
+                offDstRoot = offDst = 2;
+            }
+        }
+    }
+#endif
+    /*
+     * The path is relative to the current directory, so try add it.
+     */
+    else if (*pszSrc)
+    {
+        int rc = __libc_Back_fsDirCurrentGet(pszDst, cbDst, '\0' /* current drive */, 0 /*fFlags*/);
+        if (rc == -ERANGE)
+            return _abspath_overflow();
+        if (rc == 0)
+        {
+            offDst = _abspath_slashify(pszDst, chSlash);
+            if (pszDst[offDst - 1] != chSlash)
+            {
+                _ABSPATH_ERANGE_RETURN_CHECK(offDst + 1);
+                pszDst[offDst++] = chSlash;
+            }
+
+#ifdef __KLIBC_PATH_HAVE_DRIVE_LETTERS
+            if (_fngetdrive(pszDst))
+                offDstRoot = __KLIBC_PATH_IS_SLASH(pszDst[2]) ? 3 : 2;
+            else
+#endif
+#ifdef __KLIBC_PATH_HAVE_UNC
+            if (   __KLIBC_PATH_IS_SLASH(pszDst[0])
+                && __KLIBC_PATH_IS_SLASH(pszDst[1]))
+            {
+                offDstRoot = 2 + _abspath_component_length(&pszSrc[2]);
+                if (pszDst[offDstRoot])
+                    offDstRoot += 1 + _abspath_component_length(&pszSrc[offDstRoot]);
+            }
+            else
+#endif
+                offDstRoot = 1;
+        }
+        else
+            offDstRoot = offDst = 0;
+    }
+    /*
+     * Input is empty. Return empty path and failure.
+     */
+    else
+    {
+        if (cbDst)
+            *pszDst = '\0';
+        errno = EINVAL;
+        return -1;
+    }
+
+    /*
+     * Straighten out '.', '..' and slashes.  There shall be no slash at the
+     * head of the input string at this point, that way we can more easily
+     * handle the trailing directory slash correctly.  It is also assumed that
+     * offDstRoot - 1 is a slash or similar, so we can back up all the way to
+     * offDstRoot without needing to thing about slashes.
+     */
+    int offParentDir = -1;
+    for (;;)
+    {
+        assert(!__KLIBC_PATH_IS_SLASH(*pszSrc));
+        cch = _abspath_component_length(pszSrc);
+        if (!cch)
+            break;
+
+        /*
+         * "." - No change.
+         */
+        if (cch == 1 && pszSrc[0] == '.')
+        {
+            pszSrc++;
+            if (!*pszSrc)
+            {
+                if (offDst > offDstRoot)
+                    offDst--; /* No trailing slash. */
                 break;
-              else
+            }
+            pszSrc++;
+        }
+        /*
+         * ".." - Drop the last directory in the destination path (unless we're
+         * constructing a relative path because we couldn't for some reason or
+         * other resolve the current directory).
+         */
+        else if (cch == 2 && pszSrc[0] == '.' && pszSrc[1] == '.' && offDstRoot != 0)
+        {
+            if (offDst > offDstRoot)
+            {
+                if (offParentDir > 0)
+                    offDst = offParentDir;
+                else
                 {
-                  if (i < sizeof (dir) - 1)
-                    dir[i++] = *s;
-                  ++s;
+                    pszDst[offDst - 1] = '\0'; /* terminate and drop trailing slash. */
+                    offDst = (int)(_getname(&pszDst[offDstRoot]) - pszDst);
                 }
             }
-          dir[i] = 0;
-        }
-      if (*s != 0)
-        {
-          ++s;
-          if (*s == 0)
-            trail = 1;
-        }
-    }
-  i = 0;
-  if (i+1 < size && (src_drive != 0 || !server))
-    {
-      dst[i++] = drive;
-      dst[i++] = ':';
-    }
-  if (i < size && server && src_drive == 0)
-    dst[i++] = chSlash;
-  if (i < size && !rel && dir[0] != chSlash)
-    dst[i++] = chSlash;
-  j = 0;
-  if (rel && dir[j] == chSlash)
-    ++j;
-  while (i < size && dir[j] != 0)
-    dst[i++] = dir[j++];
-  if (trail && i < size && (i == 0 || dst[i-1] != chSlash))
-    dst[i++] = chSlash;
-  if (i >= size)
-    {
-      dir[size-1] = 0;
-      errno = ERANGE;
-      return -1;
-    }
-  dst[i] = 0;
-  return 0;
+            /* else: Hit the root, it's ".." link doesn't go anywhere. So ignore it. */
+            offParentDir = -1;
+
+            pszSrc += 2;
+            if (!*pszSrc)
+            {
+                if (offDst > offDstRoot)
+                    offDst--; /* No trailing slash. */
+                break;
+            }
+            pszSrc++;
+        }
+        /*
+         * Append the component and trailing slash, if any.
+         */
+        else
+        {
+            offParentDir = offDst; /* Cache the last dir offset for efficient '..' handling. */
+            if (!pszSrc[cch])
+            {
+                _ABSPATH_ERANGE_RETURN_CHECK(offDst + cch);
+                memcpy(&pszDst[offDst], pszSrc, cch);
+                offDst += cch;
+                break;
+            }
+
+            _ABSPATH_ERANGE_RETURN_CHECK(offDst + cch + 1);
+            memcpy(&pszDst[offDst], pszSrc, cch);
+            offDst += cch;
+            pszDst[offDst++] = chSlash;
+            pszSrc += cch + 1;
+        }
+
+        /* Skip extra slashes before we go on to the next component or reach
+           the end of the string. */
+        _ABSPATH_SKIP_SLASHES(pszSrc);
+    }
+
+    /*
+     * Add the terminator and return.
+     */
+    if (offDst < cbDst)
+    {
+        pszDst[offDst] = '\0';
+        return 0;
+    }
+    return _abspath_overflow();
 }
+
Index: /trunk/libc/tests/libc/Makefile
===================================================================
--- /trunk/libc/tests/libc/Makefile	(revision 3924)
+++ /trunk/libc/tests/libc/Makefile	(revision 3925)
@@ -58,4 +58,5 @@
 SMOKETESTS := \
 	smoketests/64bitio-1.c \
+	smoketests/abspath-1.c \
 	smoketests/access-1.c \
 	smoketests/alloca-1.c \
Index: /trunk/libc/tests/libc/smoketests/abspath-1.c
===================================================================
--- /trunk/libc/tests/libc/smoketests/abspath-1.c	(revision 3925)
+++ /trunk/libc/tests/libc/smoketests/abspath-1.c	(revision 3925)
@@ -0,0 +1,200 @@
+/* $Id$ */
+/** @file
+ * kLibC - Testcase for _abspath().
+ *
+ * @copyright   Copyright (C) 2014 knut st. osmundsen <bird-klibc-spam-xiv@anduin.net>
+ * @licenses    MIT, BSD2, BSD3, BSD4, LGPLv2.1, LGPLv3, LGPLvFuture.
+ */
+
+
+/*******************************************************************************
+*   Header Files                                                               *
+*******************************************************************************/
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/cdefs.h>
+#include <sys/syslimits.h>
+
+
+/*******************************************************************************
+*   Defined Constants And Macros                                               *
+*******************************************************************************/
+#define LOTS_OF_DOT_DOTS "../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.."
+
+#if defined(__OS2__)
+#define __KLIBC_PATH_HAVE_DRIVE_LETTERS 1
+#define __KLIBC_PATH_HAVE_UNC 1
+#endif
+
+
+int main()
+{
+    static struct
+    {
+        const char *pszIn;
+        const char *pszOut;
+    } const s_aTests[] =
+    {
+        {  ".", "<szCwd>" },
+        {  ".", "<szCwd>" },
+        {  LOTS_OF_DOT_DOTS, "<szCwdRoot>" },
+        {  LOTS_OF_DOT_DOTS "/usr/bin", "<szCwdRoot>usr/bin" },
+        {  LOTS_OF_DOT_DOTS "/usr/bin/..", "<szCwdRoot>usr" },
+        {  LOTS_OF_DOT_DOTS "/usr/bin/..////", "<szCwdRoot>usr/" },
+        {  "./foo/bar", "<szCwd>/foo/bar" },
+#ifdef __OS2__
+        {  "/@system_root", "/@system_root" },
+        {  "/@system_root/..", "/" },
+        {  "/pipe/mypipe", "/PIPE/mypipe" },
+        {  "/pipe/mypipe/123546775/..", "/PIPE/mypipe" },
+        {  "/pipe/mypipe/123546775/../", "/PIPE/mypipe/" },
+        {  "/dev/con", "/dev/con" },
+        {  "/dev/nul", "/dev/nul" },
+        {  "/dev/null", "/dev/null" },
+        {  "/dev//null", "/dev/null" },
+        {  "/dev/\\\\\\null", "/dev/null" },
+        {  "//\\///dev/\\\\\\null", "/dev/null" },
+        {  "/dev/con/", "<szCwdRoot>dev/con/" },
+        {  "/dev/ninechars", "<szCwdRoot>dev/ninechars" },
+        {  "/dev/.", "<szCwdRoot>dev" },
+        {  "/dev/..", "<szCwdRoot>" },
+#endif
+#ifdef __KLIBC_PATH_HAVE_DRIVE_LETTERS
+        {  "c:/", "C:/" },
+        {  "c:/\\//\\//\\///", "C:/" },
+        {  "c:/\\//\\//\\///\\", "C:/" },
+        {  "c:/.", "C:/" },
+        {  "c:/../././///", "C:/" },
+        {  "c:/OS2/DLL", "C:/OS2/DLL" },
+        {  "c:/OS2/DLL/", "C:/OS2/DLL/" },
+        {  "c:/OS2/DLL//", "C:/OS2/DLL/" },
+        {  "c:/OS2/DLL//\\", "C:/OS2/DLL/" },
+        {  "c:/OS2/DLL/..", "C:/OS2" },
+        {  "c:/OS2/DLL/../", "C:/OS2/" },
+        {  "c:/OS2/DLL/..\\", "C:/OS2/" },
+        {  "c:/OS2/DLL/..\\/", "C:/OS2/" },
+        {  "c:/asdflkjh/1234567890/qwerty", "C:/asdflkjh/1234567890/qwerty" },
+        {  "c:/asdflkjh/1234567890/qwerty/.", "C:/asdflkjh/1234567890/qwerty" },
+        {  "c:/asdflkjh/1234567890/qwerty/./", "C:/asdflkjh/1234567890/qwerty/" },
+        {  "c:/asdflkjh/1234567890/qwerty/././.", "C:/asdflkjh/1234567890/qwerty" },
+        {  "c:/asdflkjh/1234567890/qwerty/././.\\", "C:/asdflkjh/1234567890/qwerty/" },
+        {  "c:/asdflkjh/1234567890/qwerty/..", "C:/asdflkjh/1234567890" },
+        {  "c:/asdflkjh/1234567890/qwerty/../", "C:/asdflkjh/1234567890/" },
+        {  "c:/asdflkjh/1234567890/qwerty/../..", "C:/asdflkjh" },
+        {  "c:/asdflkjh/1234567890/qwerty/../../", "C:/asdflkjh/" },
+        {  "c:/asdflkjh/1234567890/qwerty/../../..", "C:/" },
+        {  "c:/asdflkjh/1234567890/qwerty/../../../", "C:/" },
+        {  "c:/asdflkjh/1234567890/qwerty/../../../..", "C:/" },
+        {  "c:/asdflkjh/1234567890/qwerty/../../../../..", "C:/" },
+        {  "c:/asdflkjh/1234567890/qwerty/../../../../../..", "C:/" },
+        {  "c:/asdflkjh/1234567890/qwerty/..\\..\\..\\..\\..\\..\\", "C:/" },
+        {  "c:", "<szCwdOnC>" },
+        {  "c:.", "<szCwdOnC>" },
+        {  "c:./.", "<szCwdOnC>" },
+        {  "c:.//.", "<szCwdOnC>" },
+        {  "c:" LOTS_OF_DOT_DOTS "", "C:/" },
+        {  "c:" LOTS_OF_DOT_DOTS "/", "C:/" },
+        {  "c:" LOTS_OF_DOT_DOTS "////\\", "C:/" },
+        {  "c:" LOTS_OF_DOT_DOTS "/OS2/DLL", "C:/OS2/DLL" },
+        {  "c:" LOTS_OF_DOT_DOTS "/OS2/DLL/../", "C:/OS2/" },
+#endif
+#ifdef __KLIBC_PATH_HAVE_UNC
+        {  "//server", "//server" },
+        {  "\\\\server", "//server" },
+        {  "//server/share", "//server/share" },
+        {  "//server//share/", "//server/share/" },
+        {  "//server////share///\\", "//server/share/" },
+        {  "//server/share/..", "//server/share/" },
+        {  "//server/share/../", "//server/share/" },
+        {  "//server/share/../../../..", "//server/share/" },
+        {  "//server/share/../../../../", "//server/share/" },
+        {  "//server/share/.././.././../../", "//server/share/" },
+        {  "//server/share/./.././.././../../", "//server/share/" },
+        {  "//server/share/asdf/qwerty", "//server/share/asdf/qwerty" },
+        {  "//server/////share/\\//asdf//\\\\/qwerty", "//server/share/asdf/qwerty" },
+        {  "//server/////share/\\//asdf//\\\\/qwerty/", "//server/share/asdf/qwerty/" },
+        {  "//server/share/asdf/../qwerty", "//server/share/qwerty" },
+        {  "//server/share/asdf/../qwerty/./", "//server/share/qwerty/" },
+        {  "//server/share/asdf/../../../qwerty", "//server/share/qwerty" },
+        {  "//server/share/asdf/../../../qwerty/", "//server/share/qwerty/" },
+        {  "//server/share/asdf/qwerty/../..", "//server/share/" },
+        {  "//server/share/asdf/qwerty/../../", "//server/share/" },
+        {  "//server/share/asdf/qwerty\\..\\./../..", "//server/share/" },
+        {  "//server/share/asdf/qwerty\\..\\./../../", "//server/share/" },
+#endif
+    };
+
+    char szCwd[PATH_MAX] = {0};
+    getcwd(szCwd, sizeof(szCwd));
+
+    char szCwdRoot[32] = {0};
+#ifdef __KLIBC_PATH_HAVE_DRIVE_LETTERS
+    if (_fngetdrive(szCwd) > 0)
+    {
+        strncpy(szCwdRoot, szCwd, 3);
+        szCwdRoot[3] = '\0';
+    }
+    else
+#endif
+        strcpy(szCwdRoot, "/");
+
+#ifdef __KLIBC_PATH_HAVE_DRIVE_LETTERS
+    char szCwdOnC[PATH_MAX] = {0};
+    _getdcwd('C' - 'A' + 1, szCwdOnC, sizeof(szCwdOnC));
+#endif
+
+    unsigned cErrors = 0;
+    unsigned i;
+    for (i = 0; i < sizeof(s_aTests) / sizeof(s_aTests[0]); i++)
+    {
+        char szResult[PATH_MAX];
+        int rc = _abspath(szResult, s_aTests[i].pszIn, sizeof(szResult));
+        if (!rc)
+        {
+            char szExpected[PATH_MAX];
+            if (strncmp(s_aTests[i].pszOut, "<szCwd>", 7) == 0)
+            {
+                strcpy(szExpected, szCwd);
+                strcat(szExpected, s_aTests[i].pszOut + 7);
+            }
+            else if (strncmp(s_aTests[i].pszOut, "<szCwdRoot>", 11) == 0)
+            {
+                strcpy(szExpected, szCwdRoot);
+                strcat(szExpected, s_aTests[i].pszOut + 11);
+            }
+#ifdef __KLIBC_PATH_HAVE_DRIVE_LETTERS
+            else if (strncmp(s_aTests[i].pszOut, "<szCwdOnC>", 10) == 0)
+            {
+                strcpy(szExpected, szCwdOnC);
+                strcat(szExpected, s_aTests[i].pszOut + 10);
+            }
+#endif
+            else
+                strcpy(szExpected, s_aTests[i].pszOut);
+            if (strcmp(szExpected, szResult) != 0)
+            {
+                printf("abstest-1: Test #%u failed.\n"
+                       "Input:    %s\n"
+                       "Result:   %s\n"
+                       "Expected: %s\n",
+                       i, s_aTests[i].pszIn, szResult, szExpected);
+                cErrors++;
+            }
+        }
+        else
+        {
+            printf("abstest-1: Test #%u failed: rc=%d errno=%d input=%s\n", i, rc, errno, s_aTests[i].pszIn);
+            cErrors++;
+        }
+    }
+
+    /*
+     * Report results.
+     */
+    if (cErrors)
+        printf("abstest-1: %d failures\n", cErrors);
+    else
+        printf("abstest-1: succeeded\n");
+    return cErrors ? 1 : 0;
+}
