/*****************************************************************************\
*                                                                             *
*   Filename	    lstat.c						      *
*									      *
*   Description:    WIN32 port of standard C library's lstat()		      *
*                   Also contains unlink() and rmdir(), which do use lstat.   *
*                                                                             *
*   Notes:	    TO DO: Make 3 versions for Windows: ANSI, WSTR, UTF8      *
*		    							      *
*   History:								      *
*    2014-02-06 JFL Created this module.				      *
*    2014-02-12 JFL Added code to filter reparse points, and keep only        *
*		    real junctions and symlinks.                              *
*    2014-02-13 JFL Moved dirent2stat() from dirent.c, as there must actually *
*		    be 4 WIN32 versions, for the four versions of struct stat.*
*    2014-02-28 JFL Added support for UTF-8 pathnames.                 	      *
*    2014-03-24 JFL Renamed "statx.h" as the standard <sys/stat.h>.	      *
*    2014-06-30 JFL Added support for 32K Unicode paths.           	      *
*                                                                             *
*        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 */

/* Microsoft C libraries include files */
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
/* MsvcLibX library extensions */
#include "msvclibx.h"
#include <sys/msvcStat.h>
#include "msvcDirent.h"
#include "msvcUnistd.h" /* For ResolveLinks() definition */
#include "msvcDebugm.h"
#include "msvcLimits.h"

#if defined(_MSDOS)
/* Make sure it's only defined it in one of the lstatxxx versions */
#if !defined(_USE_32BIT_TIME_T) && (_FILE_OFFSET_BITS == 32)

int dirent2stat(_dirent *pDirent, struct _stat *pStat) {
  memset(pStat, 0, sizeof(struct stat));

  /* Set times */
  pStat->st_mtime = Filetime2Timet(pDirent->d_date, pDirent->d_time);
  /* Size */
  pStat->st_size = pDirent->d_filesize;
  /* Standard attributes */
  pStat->st_mode |= (pDirent->d_type << 12); /* Set the 4-bit type field */
  pStat->st_mode |= _S_IREAD | _S_IWRITE | _S_IEXEC; /* Assume it's fully accessible */
  if (pDirent->d_attribs & _A_RDONLY) pStat->st_mode &= ~_S_IWRITE;
  /* DOS-specific attributes */
  if (pDirent->d_attribs & _A_HIDDEN) pStat->st_mode |= S_HIDDEN;
  if (pDirent->d_attribs & _A_ARCH) pStat->st_mode |= S_ARCHIVE;
  if (pDirent->d_attribs & _A_SYSTEM) pStat->st_mode |= S_SYSTEM;

  return 0;
}

#endif /* defined(_USE_32BIT_TIME_T) && (_FILE_OFFSET_BITS == 32) */
#endif /* defined(_MSDOS) */


#ifdef _WIN32

/* ------------ Display the *stat* macro values at compile time ------------ */

#pragma message(MACRODEF(_MSVC_stat))
#pragma message(MACRODEF(_MSVC_fstat))
#pragma message(MACRODEF(_MSVC_lstat))
#pragma message(MACRODEF(_MSVC_stat64))

#if _MSVCLIBX_STAT_DEFINED
  #pragma message(MACRODEF(_LIBX_stat))
  #pragma message(MACRODEF(_LIBX_stat64))
#endif

#pragma message(MACRODEF(stat))
#pragma message(MACRODEF(fstat))
#pragma message(MACRODEF(lstat))

#if 0
#pragma message(MACRODEF(_lstat))
#pragma message(MACRODEF(_lstati64))
#if _MSVCLIBX_STAT_DEFINED
  #pragma message(MACRODEF(_lstat_ns))
  #pragma message(MACRODEF(_lstati64_ns))
#endif
#endif

#if defined(_LARGEFILE_SOURCE64)
  #pragma message(MACRODEF(stat64))
  #pragma message(MACRODEF(fstat64))
  #pragma message(MACRODEF(lstat64))
#endif

#include <windows.h>

/*---------------------------------------------------------------------------*\
*                                                                             *
|   Function:	    lstat						      |
|									      |
|   Description:    Common definition of all _lstatXY() functions             |
|									      |
|   Parameters:     const char *path		The symlink name	      |
|		    struct stat *buf		Output buffer		      |
|									      |
|   Returns:	    0 = Success, -1 = Failure				      |
|									      |
|   Notes:	    See statx.h for a description of how the stat and lstat   |
|		    macros work.					      |
|									      |
|   History:								      |
|    2014-02-06 JFL Created this routine                               	      |
|    2014-02-28 JFL Added support for UTF-8 pathnames.                 	      |
*									      *
\*---------------------------------------------------------------------------*/

int lstat(const char *path, struct stat *pStat) {
  BOOL bDone;
  DWORD dwAttr;
  WIN32_FILE_ATTRIBUTE_DATA fileData;
  unsigned __int64 qwSize;
  int bIsJunction = FALSE;
  int bIsMountPoint = FALSE;
  DWORD dwTag = 0;
  DEBUG_CODE(
  char szTime[100];
  )
  WCHAR wszName[UNICODE_PATH_MAX];
  int n;

  DEBUG_ENTER((STRINGIZE(lstat) "(\"%s\", 0x%p);\n", path, pStat));

#if USE_MSVC_STAT
  dwAttr = GetFileAttributes(path);
  DEBUG_PRINTF(("GetFileAttributes() = 0x%lX\n", dwAttr));
  if (dwAttr == INVALID_FILE_ATTRIBUTES) {
    errno = ENOENT;
    RETURN_INT_COMMENT(-1, ("File does not exist\n"));
  }

  if (!(dwAttr & FILE_ATTRIBUTE_REPARSE_POINT)) {
    int iErr = stat(path, pStat);
    RETURN_INT(iErr);
  }
#endif

  /* Convert the pathname to a unicode string, with the proper extension prefixes if it's longer than 260 bytes */
  n = MultiByteToWidePath(CP_UTF8,		/* CodePage, (CP_ACP, CP_OEMCP, CP_UTF8, ...) */
    			  path,			/* lpMultiByteStr, */
			  wszName,		/* lpWideCharStr, */
			  UNICODE_PATH_MAX	/* cchWideChar, */
			  );
  if (!n) {
    errno = Win32ErrorToErrno();
    RETURN_INT_COMMENT(-1, ("errno=%d - %s\n", errno, strerror(errno)));
  }

  bDone = GetFileAttributesExW(wszName, GetFileExInfoStandard, &fileData);
  if (!bDone) {
    errno = Win32ErrorToErrno();
    RETURN_INT_COMMENT(-1, ("GetFileAttributesEx(); // Failed\n"));
  }
  XDEBUG_PRINTF(("GetFileAttributesEx(); // Success\n"));
  dwAttr = fileData.dwFileAttributes;
  XDEBUG_PRINTF(("dwFileAttributes = 0x%lX\n", dwAttr));
  DEBUG_CODE_IF_ON(Filetime2String(&fileData.ftLastWriteTime, szTime, sizeof(szTime)););
  XDEBUG_PRINTF(("ftLastWriteTime = %s\n", szTime));
  qwSize = ((unsigned __int64)fileData.nFileSizeHigh << 32) | fileData.nFileSizeLow;
  XDEBUG_PRINTF(("nFileSize = %I64d\n", qwSize));

  ZeroMemory(pStat, sizeof(struct stat));
  /* Set times */
#if _MSVCLIBX_STAT_DEFINED
  Filetime2Timespec(&fileData.ftCreationTime, &(pStat->st_ctim));  /* Windows = Create time; Unix = Permissions change time */
  Filetime2Timespec(&fileData.ftLastWriteTime, &(pStat->st_mtim));
  Filetime2Timespec(&fileData.ftLastAccessTime, &(pStat->st_atim));
#else
  Filetime2Timet(&fileData.ftCreationTime, &(pStat->st_ctime));  /* Windows = Create time; Unix = Permissions change time */
  Filetime2Timet(&fileData.ftLastWriteTime, &(pStat->st_mtime));
  Filetime2Timet(&fileData.ftLastAccessTime, &(pStat->st_atime));
#endif
  /* Size */
  /* NOTE: There is loss of data here if the file size is > 2GB, and off_t is 32-bits */
  pStat->st_size = (off_t)qwSize;
#if (_STAT_FILE_SIZE < 64)
#define _MAX_FILE_SIZE 0x7FFFFFFFL
  if (qwSize > _MAX_FILE_SIZE) pStat->st_size = (off_t)_MAX_FILE_SIZE;
#endif
  /* Standard attributes */
  /* File type */
check_attr_again:
  if (dwAttr & FILE_ATTRIBUTE_REPARSE_POINT) {
    /* JUNCTIONs and SYMLINKDs both have the FILE_ATTRIBUTE_DIRECTORY flag also set.
    // Test the FILE_ATTRIBUTE_REPARSE_POINT flag first, to make sure they're seen as symbolic links.
    //
    // All symlinks are reparse points, but not all reparse points are symlinks. */
    dwTag = GetReparseTagU(path);
    switch (dwTag) {
      case IO_REPARSE_TAG_MOUNT_POINT:	/* NTFS junction or mount point */
	{ /* We must read the link to distinguish junctions from mount points. */
	WCHAR wbuf[UNICODE_PATH_MAX];
	ssize_t n;
	bIsMountPoint = TRUE;
	n = readlinkW(wszName, wbuf, UNICODE_PATH_MAX);
	/* Junction targets are absolute pathnames, starting with a drive letter. Ex: C: */
	/* readlink() fails if the reparse point does not target a valid pathname */
	if (n < 0) goto this_is_not_a_symlink; /* This is not a junction. */
	bIsJunction = TRUE; /* Else this is a junction. Fall through to the symlink case. */
	} 	      
      case IO_REPARSE_TAG_SYMLINK:		/* NTFS symbolic link */
	pStat->st_mode |= S_IFLNK;		/* Symbolic link */
	break;
      default:	/* Anything else is definitely not like a Unix symlink */
this_is_not_a_symlink:
	dwAttr &= ~FILE_ATTRIBUTE_REPARSE_POINT;
	goto check_attr_again;
    }
  } else if (dwAttr & FILE_ATTRIBUTE_DIRECTORY)
    pStat->st_mode |= S_IFDIR;		/* Subdirectory */
  else if (dwAttr & FILE_ATTRIBUTE_DEVICE)
    pStat->st_mode |= S_IFCHR;		/* Device (we don't know if character or block) */
  else
    pStat->st_mode |= S_IFREG;		/* A normal file by default */
  /* pStat->st_mode |= (pDirent->d_type << 12); /* Set the 4-bit type field */
  pStat->st_mode |= _S_IREAD | _S_IWRITE | _S_IEXEC; /* Assume it's fully accessible */
  if (dwAttr & FILE_ATTRIBUTE_READONLY) pStat->st_mode &= ~_S_IWRITE;
  /* DOS-specific attributes */
  if (dwAttr & FILE_ATTRIBUTE_HIDDEN) pStat->st_mode |= S_HIDDEN;
  if (dwAttr & FILE_ATTRIBUTE_ARCHIVE) pStat->st_mode |= S_ARCHIVE;
  if (dwAttr & FILE_ATTRIBUTE_SYSTEM) pStat->st_mode |= S_SYSTEM;
  /* Windows-specific attributes */
  if (dwAttr & FILE_ATTRIBUTE_COMPRESSED) pStat->st_mode |= S_COMPRESSED;
  if (dwAttr & FILE_ATTRIBUTE_ENCRYPTED) pStat->st_mode |= S_ENCRYPTED;
  if (dwAttr & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED) pStat->st_mode |= S_NOT_CONTENT_INDEXED;
  if (dwAttr & FILE_ATTRIBUTE_OFFLINE) pStat->st_mode |= S_OFFLINE;
  if (dwAttr & FILE_ATTRIBUTE_SPARSE_FILE) pStat->st_mode |= S_SPARSE_FILE;
  if (bIsMountPoint) pStat->st_mode |= S_MOUNT_POINT; /* Will allow to distinguish junctions from mount points */
  /* if (dwAttr & FILE_ATTRIBUTE_TEMPORARY) pStat->st_mode |= S_TEMPORARY; */
  /* if (dwAttr & FILE_ATTRIBUTE_VIRTUAL) pStat->st_mode |= S_VIRTUAL; */
#if _MSVCLIBX_STAT_DEFINED
  pStat->st_Win32Attrs = dwAttr;
  pStat->st_ReparseTag = dwTag;
#endif

  RETURN_INT_COMMENT(0, ("%s  mode = 0x%04X  size = %I64d bytes\n", szTime, pStat->st_mode, qwSize));
}

#if !USE_MSVC_STAT
int stat(const char *path, struct stat *pStat) {
  char buf[UTF8_PATH_MAX];
  int iErr;

  DEBUG_ENTER((STRINGIZE(stat) "(\"%s\", 0x%p);\n", path, pStat));

  iErr = ResolveLinksU(path, buf, sizeof(buf));
  if (!iErr) lstat(buf, pStat);

  RETURN_INT(iErr);
}
#endif /* !USE_MSVC_STAT */

int dirent2stat(_dirent *pDirent, struct stat *pStat) {
  memset(pStat, 0, sizeof(struct stat));

  /* Set times */
#if _MSVCLIBX_STAT_DEFINED
  Filetime2Timespec(&pDirent->d_CreationTime, &(pStat->st_ctim));  /* Windows = Create time; Unix = Permissions change time */
  Filetime2Timespec(&pDirent->d_LastWriteTime, &(pStat->st_mtim));
  Filetime2Timespec(&pDirent->d_LastAccessTime, &(pStat->st_atim));
#else
  Filetime2Timet(&pDirent->d_CreationTime, &(pStat->st_ctime));  /* Windows = Create time; Unix = Permissions change time */
  Filetime2Timet(&pDirent->d_LastWriteTime, &(pStat->st_mtime));
  Filetime2Timet(&pDirent->d_LastAccessTime, &(pStat->st_atime));
#endif
  /* Size */
  /* NOTE: There is loss of data here if the file size is > 2GB, and off_t is 32-bits */
  pStat->st_size = (off_t)(pDirent->d_filesize);
#if (_STAT_FILE_SIZE < 64)
#define _MAX_FILE_SIZE 0x7FFFFFFFL
  if (pDirent->d_filesize > _MAX_FILE_SIZE) pStat->st_size = (off_t)_MAX_FILE_SIZE;
#endif
  /* Standard attributes */
  pStat->st_mode |= (pDirent->d_type << 12); /* Set the 4-bit type field */
  pStat->st_mode |= _S_IREAD | _S_IWRITE | _S_IEXEC; /* Assume it's fully accessible */
  if (pDirent->d_attribs & FILE_ATTRIBUTE_READONLY) pStat->st_mode &= ~_S_IWRITE;
  /* DOS-specific attributes */
  if (pDirent->d_attribs & FILE_ATTRIBUTE_HIDDEN) pStat->st_mode |= S_HIDDEN;
  if (pDirent->d_attribs & FILE_ATTRIBUTE_ARCHIVE) pStat->st_mode |= S_ARCHIVE;
  if (pDirent->d_attribs & FILE_ATTRIBUTE_SYSTEM) pStat->st_mode |= S_SYSTEM;
  /* Windows-specific attributes */
  if (pDirent->d_attribs & FILE_ATTRIBUTE_COMPRESSED) pStat->st_mode |= S_COMPRESSED;
  if (pDirent->d_attribs & FILE_ATTRIBUTE_ENCRYPTED) pStat->st_mode |= S_ENCRYPTED;
  if (pDirent->d_attribs & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED) pStat->st_mode |= S_NOT_CONTENT_INDEXED;
  if (pDirent->d_attribs & FILE_ATTRIBUTE_OFFLINE) pStat->st_mode |= S_OFFLINE;
  if (pDirent->d_attribs & FILE_ATTRIBUTE_SPARSE_FILE) pStat->st_mode |= S_SPARSE_FILE;
  /* Special case of junction and mount points */
  if (pDirent->d_ReparseTag) pStat->st_mode |= S_MOUNT_POINT;
  /* if (pDirent->d_attribs & FILE_ATTRIBUTE_TEMPORARY) pStat->st_mode |= S_TEMPORARY; */
  /* if (pDirent->d_attribs & FILE_ATTRIBUTE_VIRTUAL) pStat->st_mode |= S_VIRTUAL; */
#if _MSVCLIBX_STAT_DEFINED
  pStat->st_Win32Attrs = pDirent->d_attribs;
  pStat->st_ReparseTag = pDirent->d_ReparseTag;
#endif

  return 0;
}

/*---------------------------------------------------------------------------*\
*                                                                             *
|   Function:	    unlink						      |
|									      |
|   Description:    Remove a file or a symbolic link		              |
|									      |
|   Parameters:     const char *path		The file or symlink name      |
|									      |
|   Returns:	    0 = Success, -1 = Failure				      |
|									      |
|   Notes:	    							      |
|									      |
|   History:								      |
|    2014-02-17 JFL Created this routine                               	      |
|    2014-02-28 JFL Added support for UTF-8 pathnames.                 	      |
*									      *
\*---------------------------------------------------------------------------*/

int unlink(const char *path) {
  int iErr;
  BOOL bDone;
  struct stat st;
  WCHAR wszName[UNICODE_PATH_MAX];
  int n;

  DEBUG_ENTER(("unlink(\"%s\");\n", path));

  iErr = lstat(path, &st);
  if (iErr) RETURN_INT(iErr);

  if ((!S_ISREG(st.st_mode)) && (!S_ISLNK(st.st_mode))) {
    errno = ENOENT;
    RETURN_INT_COMMENT(-1, ("Pathname exists, but is not a file or a link\n"));
  }

  /* Convert the pathname to a unicode string, with the proper extension prefixes if it's longer than 260 bytes */
  n = MultiByteToWidePath(CP_UTF8,		/* CodePage, (CP_ACP, CP_OEMCP, CP_UTF8, ...) */
    			  path,			/* lpMultiByteStr, */
			  wszName,		/* lpWideCharStr, */
			  UNICODE_PATH_MAX	/* cchWideChar, */
			  );
  if (!n) {
    errno = Win32ErrorToErrno();
    RETURN_INT_COMMENT(-1, ("errno=%d - %s\n", errno, strerror(errno)));
  }

#if _MSVCLIBX_STAT_DEFINED
  if (S_ISLNK(st.st_mode) && (st.st_Win32Attrs & FILE_ATTRIBUTE_DIRECTORY)) {
    /* This link is a junction or a symlinkd */
    bDone = RemoveDirectoryW(wszName);
  } else
#endif
  bDone = DeleteFileW(wszName);

  if (bDone) {
    RETURN_INT_COMMENT(0, ("Success\n"));
  } else {
    errno = Win32ErrorToErrno();
    RETURN_INT_COMMENT(-1, ("Failed\n"));
  }
}

/*---------------------------------------------------------------------------*\
*                                                                             *
|   Function:	    rmdir						      |
|									      |
|   Description:    Remove a directory				              |
|									      |
|   Parameters:     const char *path		The directory name	      |
|									      |
|   Returns:	    0 = Success, -1 = Failure				      |
|									      |
|   Notes:	    							      |
|									      |
|   History:								      |
|    2014-03-05 JFL Created this routine with support for UTF-8 pathnames.    |
*									      *
\*---------------------------------------------------------------------------*/

int rmdir(const char *path) {
  int iErr;
  BOOL bDone;
  struct stat st;
  WCHAR wszName[UNICODE_PATH_MAX];
  int n;

  DEBUG_ENTER(("rmdir(\"%s\");\n", path));

  iErr = lstat(path, &st);
  if (iErr) RETURN_INT(iErr);

  if (!S_ISDIR(st.st_mode)) {
    errno = ENOTDIR;
    RETURN_INT_COMMENT(-1, ("Pathname exists, but is not a directory\n"));
  }

  /* Convert the pathname to a unicode string, with the proper extension prefixes if it's longer than 260 bytes */
  n = MultiByteToWidePath(CP_UTF8,		/* CodePage, (CP_ACP, CP_OEMCP, CP_UTF8, ...) */
    			  path,			/* lpMultiByteStr, */
			  wszName,		/* lpWideCharStr, */
			  UNICODE_PATH_MAX	/* cchWideChar, */
			  );
  if (!n) {
    errno = Win32ErrorToErrno();
    RETURN_INT_COMMENT(-1, ("errno=%d - %s\n", errno, strerror(errno)));
  }

  bDone = RemoveDirectoryW(wszName);

  if (bDone) {
    RETURN_INT_COMMENT(0, ("Success\n"));
  } else {
    errno = Win32ErrorToErrno();
    RETURN_INT_COMMENT(-1, ("Failed\n"));
  }
}

#endif /* _WIN32 */

