/*****************************************************************************\
*                                                                             *
*   Filename	    realpath.c						      *
*									      *
*   Description     Resolve links and remove . and .. parts in pathnames      *
*                                                                             *
*   Notes	    TO DO: Make Wide & MultiByte versions for Windows	      *
*                                                                             *
*                   TO DO: Microsoft provides a convenient routine, for which *
*                   we could provide a MultiByte version: (in MSVC's stdlib.h)*
*    char *_fullpath(char *absPath, const char *relPath, size_t maxLength);   *
*                                                                             *
*   History								      *
*    2014-02-10 JFL Created this module with routine ResolveLinks() for Win32.*
*    2014-02-19 JFL Added OS-independant CompactPath() subroutine. 	      *
*    2014-02-20 JFL Implemented realpath() for both DOS and Windows.	      *
*    2014-03-06 JFL Check for buffer overflows, and return ENAMETOOLONG.      *
*    2014-07-02 JFL Added support for pathnames >= 260 characters. 	      *
*    2016-08-25 JFL Added routine ResolveLinksA().			      *
*    2016-09-12 JFL Moved GetFileAttributesU() to its own module.	      *
*                   Bug fix: Add the drive letter if it's not specified.      *
*                   Bug fix: Detect and report output buffer overflows.       *
*                   Convert short WIN32 paths to long paths.                  *
*    2016-09-13 JFL Resize output buffers, to avoid wasting lots of memory.   *
*                                                                             *
*         Copyright 2016 Hewlett Packard Enterprise Development LP          *
* Licensed under the Apache 2.0 license - www.apache.org/licenses/LICENSE-2.0 *
\*****************************************************************************/

#define _CRT_SECURE_NO_WARNINGS 1 /* Avoid Visual C++ security warnings */

#define _UTF8_SOURCE /* Generate the UTF-8 version of routines */

#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <direct.h>	/* For _getdcwd() */
#include <ctype.h>	/* For toupper() */
#include "msvcDebugm.h"
#include "msvcLimits.h"
#include "msvcUnistd.h"
#include "msvcWindows.h"

#define TRUE 1
#define FALSE 0

/*---------------------------------------------------------------------------*\
*                                                                             *
|   Function	    CompactPath						      |
|									      |
|   Description	    Remove all ., .., and extra / or \ separators in a path   |
|									      |
|   Parameters	    const char *path	    Pathname to cleanup 	      |
|		    char *buf		    Output buffer    		      |
|		    size_t bufsize	    Size of the output buffer         |
|									      |
|   Notes	    Allows having path = outbuf, ie. in-place compacting.     |
|		    Supports both relative and absolute paths.		      |
|									      |
|   Returns	    0 = success, or -1 = error, with errno set		      |
|									      |
|   History								      |
|    2014-02-19 JFL Created this routine                                      |
*									      *
\*---------------------------------------------------------------------------*/

int CompactPath(const char *path, char *outbuf, size_t bufsize) {
  const char *pcIn;
  char *pcOut;
  int i, j, inSize, outSize;
  char c = '\0';
  char lastc = '\0';
#define MAX_SUBDIRS (PATH_MAX / 2) /* Worst case is: \1\1\1\1\1\1... */
  const char *pParts[MAX_SUBDIRS];
  int lParts[MAX_SUBDIRS];
  int lPart = 0;
  int nParts = 0;
  int isAbsolute = FALSE;
  int nDotDotParts = 0;

  pcIn = path;
  inSize = (int)strlen(path) + 1;
  pcOut = outbuf;
  outSize = (int)bufsize;

  if (*pcIn && (pcIn[1] == ':')) { /* There's a drive letter */
    *(pcOut++) = *(pcIn++);		/* Copy it */
    *(pcOut++) = *(pcIn++);
    inSize -= 2;
    outSize -= 2;
  }

  /* Scan the input pathname, recording pointers to every part of the pathname */
  for (i=0; i<inSize; i++) {
    c = pcIn[i];
    if (c == '/') c = '\\';
    if ((c == '\\') && (lastc == '\\')) continue; /* Condense multiple \ into one */
    if ((c == '\\') && (lastc == '\0')) { /* This is an absolute pathname */
      isAbsolute = TRUE;
      lastc = c;
      continue;
    }
    if ((c != '\\') && ((lastc == '\\') || (lastc == '\0'))) { /* Beginning of a new node */
      if (nParts == MAX_SUBDIRS) {
	errno = ENAMETOOLONG;
	return -1;
      }
      pParts[nParts] = pcIn + i;
      lPart = 0;
    }
    if (c && (c != '\\')) {
      lPart += 1;
    } else { /* End of a node */
      lParts[nParts++] = lPart;
      XDEBUG_PRINTF(("pParts[%d] = \"%.*s\"\n", nParts-1, lPart, pParts[nParts-1]));
    }
    lastc = c;
    if (c == '\0') break;
  }

  /* Eliminate . and .. parts */
  for (i=0; i<nParts; i++) {
    XDEBUG_PRINTF(("for pParts[%d] = \"%.*s\"\n", i, lParts[i], pParts[i]));
    if ((pParts[i][0] == '.') && (lParts[i] == 1)) { /* It's a . part */
its_a_dot_part:
      XDEBUG_PRINTF(("It's a '.'. Removing part #%d\n", i));
      nParts -= 1;
      for (j=i; j<nParts; j++) {
      	pParts[j] = pParts[j+1];
      	lParts[j] = lParts[j+1];
      }
      i -= 1;
      continue;
    }
    if ((pParts[i][0] == '.') && (pParts[i][1] == '.') && (lParts[i] == 2)) { /* It's a .. part */
      if (i == nDotDotParts) {
	XDEBUG_PRINTF(("It's a '..', but it's at the root.\n"));
      	if (isAbsolute) goto its_a_dot_part; /* .. in root is like . */
      	nDotDotParts += 1; /* Else for relative paths, keep this .. */
      	continue;
      }
      XDEBUG_PRINTF(("It's a '..'. Removing parts #%d and #%d\n", i-1, i));
      nParts -= 2;
      for (j=i-1; j<nParts; j++) {
      	pParts[j] = pParts[j+2];
      	lParts[j] = lParts[j+2];
      }
      i -= 2;
      continue;
    }
  }

  /* Join all remaining parts into a single path */
  if (!outSize) {
not_compact_enough:
    errno = ENAMETOOLONG;
    return -1;
  }
  outSize -= 1;	/* Leave room for the final NUL */
  if (isAbsolute) {
    if (!outSize) goto not_compact_enough;
    *(pcOut++) = '\\';
    outSize -= 1;
  }
  if (nParts) {
    /* Copy part 0 if it's not already at the right place */
    if (lParts[0] > outSize) goto not_compact_enough;
    if (pcOut != pParts[0]) strncpy(pcOut, pParts[0], lParts[0]);
    pcOut += lParts[0];
    outSize -= lParts[0];
  } else {
    if (pcOut == outbuf) {
      if (!outSize) goto not_compact_enough;
      *(pcOut++) = '.'; /* Special case for "subdir\..", which is "." and not "" */
      outSize -= 1;
    }
  }
  for (i=1; i<nParts; i++) {
    if (!outSize) goto not_compact_enough;
    *(pcOut++) = '\\';
    outSize -= 1;
    if (lParts[i] > outSize) goto not_compact_enough;
    if (pcOut != pParts[i]) strncpy(pcOut, pParts[i], lParts[i]);
    pcOut += lParts[i];
    outSize -= lParts[i];
  }
  *pcOut = '\0';

  return (int)(pcOut - outbuf);
}


#ifdef _MSDOS

/* Standard C library routine realpath() */
/* Normally defined in stdlib.h. Output buf must contain PATH_MAX bytes */
char *realpath(const char *path, char *outbuf) {
  char *pOutbuf = outbuf;
  int iErr;
  const char *pc;

  if (!pOutbuf) pOutbuf = malloc(PATH_MAX);
  if (!pOutbuf) {
    errno = ENOMEM;
    return NULL;
  }

  /* Convert relative paths to absolute paths */
  pc = path;
  if (pc[0] && (pc[1] == ':')) pc += 2; /* Skip the drive letter, if any */
  if ((*pc != '/') && (*pc != '\\')) { /* This is a relative path */
    int iDrive = 0;
    if (pc != path) { /* A drive was specified */
      iDrive = _toupper(path[0]) - '@'; /* A=1, B=2, ... */
    }
    _getdcwd(iDrive, pOutbuf, PATH_MAX);
    if ((strlen(pOutbuf) + strlen(pc) + 2) > PATH_MAX) {
realpath_failed:
      errno = ENAMETOOLONG;
      if (!outbuf) free(pOutbuf);
      return NULL;
    }
    strcat(pOutbuf, "\\");
    strcat(pOutbuf, pc);
    path = pOutbuf;
  } else if (pc == path) { /* This is an absolute path without a drive letter */
    pOutbuf[0] = (char)(_getdrive() + 0x40);
    pOutbuf[1] = ':';
    if ((strlen(path) + 3) > PATH_MAX) goto realpath_failed;
    strcpy(pOutbuf+2, path);
    path = pOutbuf;
  }

  /* TO DO: Resolve substituted drives */  

  /* TO DO: Convert short paths to long paths, and correct the name case */  

  /* Remove useless parts in the absolute path */
  iErr = CompactPath(path, pOutbuf, PATH_MAX);
  if (iErr == -1) { /* CompactPath() sets errno */
    if (!outbuf) free(pOutbuf);
    return NULL;
  }

  if (!outbuf) pOutbuf = realloc(pOutbuf, strlen(pOutbuf) + 1);
  return pOutbuf;
}

#endif


#ifdef _WIN32

#include <windows.h>	/* Also includes MsvcLibX' WIN32 UTF-8 extensions */

/*---------------------------------------------------------------------------*\
*                                                                             *
|   Function	    ResolveLinks					      |
|									      |
|   Description	    Resolve all link names within a pathname		      |
|									      |
|   Parameters	    const char *path	    Pathname to resolve 	      |
|		    char *buf		    Output buffer    		      |
|		    size_t bufsize	    Size of the output buffer         |
|									      |
|   Notes	    Description of the official path resolution process:      |
|		    http://man7.org/linux/man-pages/man7/path_resolution.7.html
|									      |
|		    This function always returns a valid string in the output |
|		    buffer, even when it fails. This allows displaying a clue |
|		    about at which stage of the resolution things went wrong. |
|									      |
|   Returns	    0 = success, or -1 = error, with errno set		      |
|									      |
|   History								      |
|    2014-02-07 JFL Created this routine                                      |
|    2014-07-02 JFL Added support for pathnames >= 260 characters. 	      |
*									      *
\*---------------------------------------------------------------------------*/

/* Linked list of previous pathnames */
typedef struct _NAMELIST {
  struct _NAMELIST *prev;
  char *path;
} NAMELIST;

/* Get the canonic name of a file, after resolving all links in its pathname */
int ResolveLinksU1(const char *path, char *buf, size_t bufsize, NAMELIST *prev, int iDepth) {
  char target[UTF8_PATH_MAX];
  int i;
  int iErr = 0;
  DWORD dwAttr;
  char c = '\0';
  int iPath = 0;
  int bFirst = 1;
  ssize_t nRead;
  int iBuf = 0;
  NAMELIST list;
  NAMELIST *pList;

  DEBUG_ENTER(("ResolveLinks1(\"%s\", 0x%p, %ld, 0x%p, %d);\n", path, buf, bufsize, prev, iDepth));

  while (path[iPath]) {
    /* int iPath0 = iPath; */
    /* int iBuf0 = iBuf; */
    int iBuf1;
    /* Append the first name section to the output buffer */
    if (bFirst) {
      bFirst = 0;
      /* Special case of absolute pathnames */
      if (path[0] == '\\') {
	if ((iBuf+1U) >= bufsize) {
resolves_too_long:
	  errno = ENAMETOOLONG;
	  RETURN_INT_COMMENT(-1, ("Name too long\n"));
	}
	c = buf[iBuf++] = path[iPath++]; /* Copy the root \ . */
	/* DEBUG_PRINTF(("buf[%d] = %c\n", iBuf-1, c)); */
      } else if (path[0] && (path[1] == ':')) {
	if ((iBuf+1U) >= bufsize) goto resolves_too_long;
	c = buf[iBuf++] = path[iPath++]; /* Copy the drive letter */
	/* DEBUG_PRINTF(("buf[%d] = %c\n", iBuf-1, c)); */
	if ((iBuf+1U) >= bufsize) goto resolves_too_long;
	c = buf[iBuf++] = path[iPath++]; /*      and the : */
	/* DEBUG_PRINTF(("buf[%d] = %c\n", iBuf-1, c)); */
	if (path[iPath] == '\\') {
	  if ((iBuf+1U) >= bufsize) goto resolves_too_long;
	  c = buf[iBuf++] = path[iPath++];
	  /* DEBUG_PRINTF(("buf[%d] = %c\n", iBuf-1, c)); */
	}
      } /* Else it's a relative pathname, handled in the common code below */
    } else { /* It's a continuation section in the path */
      if ((iBuf+1U) >= bufsize) goto resolves_too_long;
      buf[iBuf++] = '\\';
    }
    iBuf1 = iBuf; /* Index of the beginning of the node name we're about to add */
    for (i=0; (size_t)(iBuf+i) < (bufsize-1) ;i++) {
      c = path[iPath+i];
      if (!c) break;
      /* if (c == '/') break; */
      if (c == '\\') break;
      /* DEBUG_PRINTF(("buf[%d] = %c\n", iBuf+i, c)); */
      buf[iBuf+i] = c;
    }
    iBuf += i;
    buf[iBuf] = '\0';
    while (c && (/* (c == '/') || */ (c == '\\'))) c=path[iPath + ++i]; /* Skip extra /, if any */
    iPath += i;
    /* DEBUG_PRINTF(("// Removed %d characters from path\n", iPath - iPath0)); */
    /* Get the file type */
    dwAttr = GetFileAttributesU(buf);
    DEBUG_PRINTF(("// \"%s\" is %s\n", buf, (dwAttr == INVALID_FILE_ATTRIBUTES)?"not found":(
					    (dwAttr & FILE_ATTRIBUTE_REPARSE_POINT)?"a reparse point":(
      					    (dwAttr & FILE_ATTRIBUTE_DIRECTORY)?"a directory":"a file"))));
    if (dwAttr == INVALID_FILE_ATTRIBUTES) {
      errno = ENOENT;
      if (path[iPath]) { /* Append the remainder of the input path, even though we know it's invalid */
      	if ((iBuf+1U+lstrlen(path+iPath)) < bufsize) {
	  buf[iBuf++] = '\\';
	  lstrcpy(buf+iBuf, path+iPath);
	}
      }
      RETURN_INT_COMMENT(-1, ("No such file: \"%s\"\n", buf));
    }
    if (dwAttr & FILE_ATTRIBUTE_REPARSE_POINT) {
      nRead = readlinkU(buf, target, sizeof(target));
      if (nRead == -1) {
	if (errno == EINVAL) { /* This is a reparse point, but not a symlink or a junction */
	  goto file_or_directory;
	}
	/* Anything else is a real error, and resolution cannot be done. */
	if (path[iPath]) { /* Append the remainder of the input path, even though we know it's invalid */
	  if ((iBuf+1U+lstrlen(path+iPath)) < bufsize) {
	    buf[iBuf++] = '\\';
	    lstrcpy(buf+iBuf, path+iPath);
	  }
	}
        RETURN_INT_COMMENT(-1, ("Dangling link: \"%s\"\n", buf));
      }
      if ((dwAttr & FILE_ATTRIBUTE_DIRECTORY) && !lstrcmp(buf, target)) {
      	/* This is probably a junction pointing to an otherwise inaccessible area.
      	   (See readlink() header in readlink.c for details about this case.)
      	   Handle this junction like if it were a real directory, instead of a symlink. */
	goto file_or_directory;
      }
      if (   ((target[0] == '\\') /* || (target[0] == '/') */ )
	  || (target[1] == ':')) { /* This is an absolute pathname */
	DEBUG_PRINTF(("// Absolute link to \"%s\"\n", target));
	iBuf = (int)lstrlen(target); /* Anticipate the new position after copying */
	if ((size_t)iBuf >= bufsize) goto resolves_too_long;
	lstrcpy(buf, target);
      } else { /* This is a relative pathname */
	DEBUG_PRINTF(("// Relative link to \"%s\"\n", target));
	/* So it'll replace the tail name in the output path */
	iBuf = iBuf1; /* The index right after the last path separator */
	DEBUG_PRINTF(("AppendPath(\"%.*s\", \"%s\"); // Target tail\n", iBuf, buf, target));
	if (((size_t)iBuf+lstrlen(target)) >= bufsize) goto resolves_too_long;
	lstrcpy(buf+iBuf, target);
	iBuf = CompactPath(buf, buf, bufsize);
      }
      /* Append the remainder of the input path, if any */
      DEBUG_PRINTF(("AppendPath(\"%s\", \"%s\"); // Path tail\n", buf, path+iPath));
      if (path[iPath]) {
	if (iBuf && (buf[iBuf-1] != '\\')) {
	  if ((iBuf+1U) >= bufsize) goto resolves_too_long;
	  buf[iBuf++] = '\\';
	}
	if (((size_t)iBuf+lstrlen(path+iPath)) >= bufsize) goto resolves_too_long;
	lstrcpy(buf+iBuf, path+iPath);
	iBuf = CompactPath(buf, buf, bufsize);
      }
      /* Check for max depth */
      if (iDepth == SYMLOOP_MAX) {
	errno = ELOOP;
	RETURN_INT_COMMENT(-1, ("Max symlink depth reached: \"%s\"\n", buf));
      }
      /* Check for loops */
      for (pList = prev ; pList; pList = pList->prev) {
      	if (!lstrcmpi(buf, pList->path)) {
      	  errno = ELOOP;
	  RETURN_INT_COMMENT(-1, ("Loop found: \"%s\"\n", buf));
	}
      }
      /* OK, no loop, so repeat the process for that new path */
      lstrcpy(target, buf); /* Keep that as a reference in the linked list, in case there are further links */
      list.prev = prev;
      list.path = target;
      iErr = ResolveLinksU1(target, buf, bufsize, &list, iDepth+1);
      RETURN_INT_COMMENT(iErr, ("\"%s\"\n", buf));
    } else { /* It's a normal file or directory */
file_or_directory:
      if ((path[iPath]) && !(dwAttr & FILE_ATTRIBUTE_DIRECTORY)) {
      	errno = ENOTDIR;
	if ((iBuf+1U+lstrlen(path+iPath)) < bufsize) {
	  buf[iBuf++] = '\\';
	  lstrcpy(buf+iBuf, path+iPath);
	}
	RETURN_INT_COMMENT(-1, ("File where dir expected: \"%s\"\n", buf));
      }
    }
  }

  RETURN_INT_COMMENT(0, ("Success: \"%s\"\n", buf));
}

int ResolveLinksU(const char *path, char *buf, size_t bufsize) {
  char path1[UTF8_PATH_MAX];
  int nSize;
  NAMELIST root;
  int iErr;

  DEBUG_ENTER(("ResolveLinks(\"%s\", 0x%p, %ld);\n", path, buf, bufsize));

  buf[0] = '\0'; /* Always output a valid string */

  if (!*path) { /* Spec says an empty pathname is invalid */
    errno = ENOENT;
    RETURN_INT_COMMENT(-1, ("Empty pathname\n"));
  }

  /* Normalize the input path, using a single \ as path separator */
  nSize = CompactPath(path, path1, sizeof(path1));
  if (nSize == -1) { /* CompactPath() already sets errno = ENAMETOOLONG */
    RETURN_INT_COMMENT(-1, ("Path too long\n"));
  }
  if (path1[nSize-1] == '\\') { /* Spec says resolution must implicitly add a trailing dot, */
    if (nSize == sizeof(path1)) {    /*  to ensure that the last component is a directory. */
      errno = ENAMETOOLONG;
      RETURN_INT_COMMENT(-1, ("Path too long after adding .\n"));
    }
    path1[nSize++] = '.'; /* So add it explicitely */
    path1[nSize] = '\0';
  }
  root.path = path1;
  root.prev = NULL;
  iErr = ResolveLinksU1(path1, buf, bufsize, &root, 0);
  nSize = lstrlen(buf);
  /* Remove the final dot added above, if needed. */
  if ((nSize >= 2) && (buf[nSize-2] == '\\') && (buf[nSize-1] == '.')) buf[--nSize] = '\0';
  RETURN_INT_COMMENT(iErr, ("\"%s\"\n", buf));
}

/* ANSI version of the same, built upon the UTF-8 version */
int ResolveLinksA(const char *path, char *buf, size_t bufsize) {
  char pathU[UTF8_PATH_MAX];
  char pathU2[UTF8_PATH_MAX];
  WCHAR wszPath[PATH_MAX];
  int n;
  int iErr;

  /* Convert the pathname to a unicode string */
  n = MultiByteToWideChar(CP_ACP, 0, path, (int)strlen(path) + 1, wszPath, PATH_MAX);
  /* Convert it back to UTF-8 characters */
  if (n) n = WideCharToMultiByte(CP_UTF8, 0, wszPath, n, pathU, UTF8_PATH_MAX, NULL, NULL);
  /* Check (unlikely) conversion errors */
  if (!n) {
    errno = Win32ErrorToErrno();
    RETURN_INT_COMMENT(-1, ("errno=%d - %s\n", errno, strerror(errno)));
  }

  /* Resolve the links */
  iErr = ResolveLinksU(pathU, pathU2, UTF8_PATH_MAX);

  /* Convert the result back to ANSI */
  if (!iErr) {
    n = MultiByteToWideChar(CP_UTF8, 0, pathU2, (int)strlen(pathU2) + 1, wszPath, PATH_MAX);
    if (n) n = WideCharToMultiByte(CP_ACP, 0, wszPath, n, buf, (int)bufsize, NULL, NULL);
    if (!n) {
      errno = Win32ErrorToErrno();
      RETURN_INT_COMMENT(-1, ("errno=%d - %s\n", errno, strerror(errno)));
    }
  }

  return iErr;
}

/* Normally defined in stdlib.h. Output buf must contain PATH_MAX bytes */
char *realpathU(const char *path, char *outbuf) {
  char *pOutbuf = outbuf;
  char *pPath1 = NULL;
  char *pPath2 = NULL;
  int iErr;
  const char *pc;
  size_t nSize;
  DEBUG_CODE(
  char *pszCause = "Out of memory";
  )
  int n;

  DEBUG_ENTER(("realpath(\"%s\", 0x%p);\n", path, outbuf));

  if (!pOutbuf) pOutbuf = malloc(UTF8_PATH_MAX);
  if (!pOutbuf) {
realpathU_failed:
    if (!outbuf) free(pOutbuf);
    free(pPath1);
    free(pPath2);
    errno = ENOMEM;
    RETURN_CONST_COMMENT(NULL, ("%s\n", pszCause));
  }

  pPath1 = malloc(UTF8_PATH_MAX);
  if (!pPath1) goto realpathU_failed;

  pPath2 = malloc(UTF8_PATH_MAX);
  if (!pPath2) goto realpathU_failed;

  /* Convert relative paths to absolute paths */
  pc = path;
  if (pc[0] && (pc[1] == ':')) pc += 2; /* Skip the drive letter, if any */
  if ((*pc != '/') && (*pc != '\\')) { /* This is a relative path */
    int iDrive = 0;
    if (pc != path) { /* A drive was specified */
      iDrive = toupper(path[0]) - '@'; /* A=1, B=2, ... */
    }
    _getdcwdU(iDrive, pPath1, UTF8_PATH_MAX);
    nSize = UTF8_PATH_MAX - lstrlen(pPath1);
    if ((lstrlen(pc) + 2U) > nSize) {
      errno = ENAMETOOLONG;
      DEBUG_CODE(pszCause = "Path too long after concatenating current dir");
      goto realpathU_failed; 
    }
    strcat(pPath1, "\\");
    strcat(pPath1, pc);
    path = pPath1;
  } else if (pc == path) { /* This is an absolute path without a drive letter */
    pPath1[0] = (char)(_getdrive() + 0x40);
    pPath1[1] = ':';
    if (strlen(path) > (UTF8_PATH_MAX-3)) {
      errno = ENAMETOOLONG;
      DEBUG_CODE(pszCause = "Path too long after adding drive");
      goto realpathU_failed;
    }
    strcpy(pPath1+2, path);
    path = pPath1;
  }    

  /* Resolve links in the absolute path */
  iErr = ResolveLinksU(path, pPath2, UTF8_PATH_MAX);
  if (iErr == -1) {
    DEBUG_CODE(pszCause = "Resolution failed");
    goto realpathU_failed;
  }

  /* Change short names to long names, and correct the name case */
  n = GetLongPathNameU(pPath2, pOutbuf, UTF8_PATH_MAX); /* This will NOT correct long names case */
  if (!n) {
    DEBUG_CODE(pszCause = "Can't get long pathnames";)
    goto realpathU_failed;
  }

  DEBUG_LEAVE(("return 0x%p; // \"%s\"\n", pOutbuf, pOutbuf));
  if (!outbuf) pOutbuf = realloc(pOutbuf, strlen(pOutbuf) + 1);
  free(pPath1);
  free(pPath2);
  return pOutbuf;
}

#endif /* defined(_WIN32) */

