/*****************************************************************************\
*                                                                             *
*   Filename:	    dirent.c						      *
*                                                                             *
*   Description:    DOS/WIN32 port of standard C library's dirent.h functions *
*                                                                             *
*   Notes:	    TO DO: Make Wide & MultiByte versions of scandir 4 Windows*
*                                                                             *
*		    There are also remains of an OS/2 implementation here.    *
*		    It's not maintained anymore. Left in as a historic        *
*		    reference, in the unlikely case somebody needs it.        *
*									      *
*   History:								      *
*    2012-01-09 JFL Created this file, based on dirc and other programs.      *
*    2012-01-19 JFL Added standard errno management.                          *
*    2012-05-22 JFL Fixed a bug in the DOS version, which failed if the       *
*		    directory name was longer than 12 bytes.                  *
*    2012-05-23 JFL Fixed opendir to return errors as per the C lib spec.     *
*    2013-02-12 JFL Added code to filter reparse points, and keep only        *
*		    real junctions and symlinks.                              *
*    2013-02-13 JFL Moved dirent2stat() to lstat.c, as there must actually    *
*		    be 4 WIN32 versions, for the four versions of struct stat.*
*    2013-02-26 JFL Moved the proprietary file time management routines to    *
*		    the new filetime.c module.				      *
*    2014-02-27 JFL Changed the WIN32 output name encoding to UTF-8.	      *
*    2014-03-20 JFL Restructured Windows opendir and readdir functions into   *
*		    Wide and MultiByte versions, and changed the Unicode and  *
*		    Ansi versions to macros.				      *
*    2014-03-24 JFL Renamed "statx.h" as the standard <sys/stat.h>.	      *
*    2015-12-14 JFL Bug fix: WIN32 readdirW always read the root on "D:".     *
*		    Bug fix: DOS opendir failed on root dirs, like "D:\".     *
*		    							      *
*         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 WIN32 printf & scandir */

#include "msvcDirent.h" /* Include our associated .h, in the same dir as this .c. Do not use <>. */
#ifndef _DIRENT_FOR_DOS_WINDOWS
#error "This requires MsvcLibX own version of dirent.h for DOS/Windows"
#endif
/* Microsoft C libraries include files */
#include <io.h>
#include <stdlib.h>
#include <string.h>
#include <memory.h>
#include <errno.h>
/* MsvcLibX library extensions */
#include "msvcUnistd.h" /* For readlink() */
#include "sys/msvcStat.h"  /* For Filetime2String() */
#include "msvcDebugm.h" /* Use our house debugging framework */
#include "msvcLimits.h" /* Use our house debugging framework */

/*****************************************************************************\
*                                                                             *
*                               MS_DOS Version                                *
*                                                                             *
\*****************************************************************************/

#ifdef _MSDOS

#define OFFSET_OF(pointer) ((uint16_t)(uint32_t)(void far *)pointer)
#define SEGMENT_OF(pointer) ((uint16_t)(((uint32_t)(void far *)pointer) >> 16))

void put_dta(char *p_dta) {	/* Set the MS-DOS Disk Transfer Address */
  union REGS inreg;
  union REGS outreg;
  struct SREGS sregs;

  inreg.h.ah = 0x1a;
  inreg.x.dx = OFFSET_OF(p_dta);
  sregs.ds = SEGMENT_OF(p_dta);

  intdosx(&inreg, &outreg, &sregs);
}

#define CF 0x0001            /* Carry flag bit mask */

int get_file_attributes(const char *name, unsigned *pAttr) {	/* Get File Attributes */
  union REGS inreg;
  union REGS outreg;
  struct SREGS sregs;

  inreg.x.ax = 0x4300;
  inreg.x.dx = OFFSET_OF(name);
  sregs.ds = SEGMENT_OF(name);

  intdosx(&inreg, &outreg, &sregs);

  if (CF & outreg.x.cflag) {
    errno = outreg.x.ax;
    return errno;
  }

  *pAttr = outreg.x.cx;
  return 0;
}

/* Workaround for a VMWare player shared folders bug:
   DOS function 4e (search next) sometimes returns several times the same entry. */
static fileinfo previousFI;
#ifdef _DEBUG
static int report_workaround(char *s) {
  DEBUG_PRINTF((s));
  return 1;
}
#define REPORT_WORKAROUND(args) report_workaround args
#else
#define REPORT_WORKAROUND(args) 1
#endif

int srch1st(char *pszFile, uint16_t wAttr, fileinfo *pInfo) { /* Search first matching file */
  union REGS inreg;
  union REGS outreg;
  struct SREGS sregs;

  DEBUG_ENTER(("srch1st(\"%s\", 0x%04X, 0x%p);\n", pszFile, wAttr, pInfo));

  /* Make sure the DTA is assigned before calling DOS functions 4E and 4F */
  put_dta((char *)pInfo);

  inreg.h.ah = 0x4e;
  inreg.x.cx = wAttr;
  inreg.x.dx = OFFSET_OF(pszFile);
  sregs.ds = SEGMENT_OF(pszFile);

  intdosx(&inreg, &outreg, &sregs);

  if (CF & outreg.x.cflag) {
    DEBUG_LEAVE(("return %d; // DOS error code\n", outreg.x.ax));
    return (int)(outreg.x.ax);
  }

  previousFI = *pInfo; /* Save it for the workaround for the VMWare player bug */

  DEBUG_LEAVE(("return 0;  // Success\n"));
  return 0;
}

int srchnext(fileinfo *pInfo) { /* Search next matching file */
  union REGS inreg;
  union REGS outreg;

  DEBUG_ENTER(("srchnext(0x%p);\n", pInfo));

  /* Make sure the DTA is assigned before calling DOS functions 4E and 4F */
  put_dta((char *)pInfo);

  inreg.h.ah = 0x4f;

  do {
    intdos(&inreg, &outreg);

    if (CF & outreg.x.cflag) {
      DEBUG_LEAVE(("return %d; // DOS error code\n", outreg.x.ax));
      return(outreg.x.ax);
    }
  } while ((!strncmp(previousFI.fiFileName, pInfo->fiFileName, sizeof(previousFI)))
           && REPORT_WORKAROUND(("// Skipped one duplicate entry\n")));

  previousFI = *pInfo; /* Save it for the workaround for the VMWare player bug */

  DEBUG_LEAVE(("return 0;  // Success\n"));
  return 0;
}

DIR *opendir(const char *name) { /* Open a directory */
  DIR *pDir = NULL;
  size_t lName;
  unsigned attr;
  char *pszWildCards = "\\*.*";
  char *pszCopy;
  DEBUG_ENTER(("opendir(\"%s\");\n", name));
  lName = strlen(name);
  if (lName == 0) {
opendir_noent:
    errno = ENOENT;
opendir_failed:
    if (!_sys_errlist[ENOTDIR][0]) _sys_errlist[ENOTDIR] = "Not a directory"; /* Workaround for the missing entry in MSVC list */
    if (pDir) free(pDir);
    DEBUG_LEAVE(("return NULL; // errno=%d - %s\n", errno, strerror(errno)));
    return NULL;
  }
  pDir = (DIR *)malloc(sizeof(DIR) + lName + 5); /* + 5 for wildcards suffix */
  if (!pDir) goto opendir_failed;
  /* Work on a copy of the directory name */
  pszCopy = (char *)(pDir + 1);
  strcpy(pszCopy, name);
  /* First change: Except for the root, Remove the trailing \s, which confuses get_file_attributes() */
  while ((lName > 1) && (name[lName-1] == '\\') && (name[lName-2] != ':')) pszCopy[--lName] = '\0';
  if (get_file_attributes(pszCopy, &attr)) goto opendir_noent;
  if (!(attr & _A_SUBDIR)) {
    errno = ENOTDIR;
    goto opendir_failed;
  }
  if (name[lName-1] == '\\') pszWildCards += 1; /* Avoid duplicating the \ */
  strcpy(pszCopy+lName, pszWildCards);
  pDir->first = 1;
  DEBUG_LEAVE(("return 0x%p;\n", pDir));
  return pDir;
}

int closedir(DIR *pDir) { /* Close the directory. Return 0 if successful, -1 if not. */
  DEBUG_PRINTF(("closedir(0x%p);\n", pDir));
  if (pDir) free(pDir);
  return 0;
}

_dirent *readdir(DIR *pDir) { /* Read a directory entry. Return pDir, or NULL for EOF or error. */
  int iErr;
  _dirent *pDirent = &pDir->sDirent;
  fileinfo *pFI = (fileinfo *)(pDirent->d_reserved); /* Address of the fileinfo structure embedded there */
#ifdef _DEBUG
  char szTime[40];
#endif

  DEBUG_ENTER(("readdir(0x%p);\n", pDir));
  if (pDir->first) { /* First search */
    iErr = srch1st((char *)(pDir+1), 0x3F, pFI);
    pDir->first = 0;
  } else {
    iErr = srchnext(pFI);
  }
  if (!iErr) {
    pDirent->d_type = DT_REG;			/* A normal file by default */
    if (pDirent->d_attribs & _A_SUBDIR) pDirent->d_type = DT_DIR;  /* Subdirectory */
    if (pDirent->d_attribs & _A_VOLID) pDirent->d_type = DT_VOLID; /* Volume ID file */
    DEBUG_LEAVE(("return 0x%p; // %s %02X %10ld %s\n",
		  pDirent,
		  Filetime2String(pDirent->d_date, pDirent->d_time, szTime, sizeof(szTime)),
		  pDirent->d_attribs,
		  pDirent->d_filesize,
		  pDirent->d_name));
    return &pDir->sDirent;
  }
  switch (iErr) { /* Correct a few errors that do not map well to C library errors */
    case ESRCH: iErr = ENOTDIR; break;
    case EXDEV: iErr = 0; break;	/* End of files is NOT an error */
  }
  if (iErr) {
    errno = iErr; /* MS-DOS' errno.h maps C-library errnos to DOS' errors */
    DEBUG_LEAVE(("return NULL; // errno=%d - %s\n", errno, strerror(errno)));
  } else {
    DEBUG_LEAVE(("return NULL; // End of directory\n"));
  }
  return NULL;
}

#endif /* defined(_MSDOS) */

/*****************************************************************************\
*                                                                             *
*                               WIN32 Version                                 *
*                                                                             *
\*****************************************************************************/

#ifdef _WIN32

#include <windows.h>

/* Requires including windows.h and especially the kernel section */

DIR *opendirW(const WCHAR *wszName) { /* Open a directory - Wide char version */
  DIR *pDir;
  int err;
  DWORD dw;
  DEBUG_CODE(
  char szUtf8[UTF8_PATH_MAX];
  )

  DEBUG_WSTR2UTF8(wszName, szUtf8, sizeof(szUtf8));
  DEBUG_ENTER(("opendir(\"%s\");\n", szUtf8));

  dw = GetFileAttributesW(wszName);
  err = 0;
  if (dw == INVALID_FILE_ATTRIBUTES) {
    err = errno = Win32ErrorToErrno();
  } else {
    if (!(dw & _A_SUBDIR)) {
      err = errno = ENOTDIR;
    }
  }
  if (err) {
    RETURN_CONST_COMMENT(NULL, ("errno=%d - %s\n", errno, strerror(errno)));
  }
  if (lstrlenW(wszName) >= sizeof(pDir->wszDirName)) {
    errno = ENAMETOOLONG;
    RETURN_CONST_COMMENT(NULL, ("errno=%d - %s\n", errno, strerror(errno)));
  }
  pDir = malloc(sizeof(DIR));
  if (!pDir) {
    errno = ENOMEM;
    RETURN_CONST_COMMENT(NULL, ("errno=%d - %s\n", errno, strerror(errno)));
  }
  pDir->hFindFile = INVALID_HANDLE_VALUE;
  lstrcpyW(pDir->wszDirName, wszName);
  DEBUG_LEAVE(("return 0x%p; // Success\n", pDir));
  return pDir;
}

DIR *opendirM(const char *name, UINT cp) { /* Open a directory - MultiByte char version */
  WCHAR wszName[PATH_MAX];
  int n;

  /* Convert the pathname to a unicode string, with the proper extension prefixes if it's longer than 260 bytes */
  n = MultiByteToWidePath(cp,			/* CodePage, (CP_ACP, CP_OEMCP, CP_UTF8, ...) */
    			  name,			/* lpMultiByteStr, */
			  wszName,		/* lpWideCharStr, */
			  COUNTOF(wszName)	/* cchWideChar, */
			  );
  if (!n) {
    errno = Win32ErrorToErrno();
    DEBUG_PRINTF(("opendirM(\"%s\"); // Can't convert name to Unicode: errno=%d - %s\n", name, errno, strerror(errno)));
    return NULL;
  }

  return opendirW(wszName);
}

int closedir(DIR *pDir) { /* Close the directory. Return 0 if successful, -1 if not. */
  DEBUG_PRINTF(("closedir(0x%p);\n", pDir));
  if (pDir) {
    if (pDir->hFindFile != INVALID_HANDLE_VALUE) FindClose(pDir->hFindFile);
    pDir->hFindFile = INVALID_HANDLE_VALUE;
    free(pDir);
  }
  return 0;
}

/* Read a directory entry. Return pDir, or NULL for EOF or error. Wide char version. */
_dirent *readdirW(DIR *pDir) {
  int iErr = 0;
  _dirent *pDirent = &pDir->sDirent;
  int bIsJunction = FALSE;
  int bIsMountPoint = FALSE;
  DWORD dwTag = 0; /* Reparse points tag */
  DWORD dwAttr;
  int n;
  DEBUG_CODE(
  char szTime[40];
  char szUtf8[UTF8_PATH_MAX];
  )

  DEBUG_ENTER(("readdir(0x%p);\n", pDir));

  if (pDir->hFindFile == INVALID_HANDLE_VALUE) {
    WCHAR wszPattern[MAX_PATH+5];
    lstrcpyW(wszPattern, pDir->wszDirName);
    n = lstrlenW(wszPattern);
    if (n && (wszPattern[n-1] != L'\\') && (wszPattern[n-1] != L':')) wszPattern[n++] = L'\\';
    lstrcpyW(wszPattern+n, L"*.*");
    pDir->hFindFile = FindFirstFileW(wszPattern, &pDir->wfd);
    if (pDir->hFindFile == INVALID_HANDLE_VALUE) {
      iErr = Win32ErrorToErrno();
      if (!iErr) iErr = ENOENT;
    }
  } else {
    iErr = !FindNextFileW(pDir->hFindFile, &pDir->wfd);
    if (iErr) {
      iErr = Win32ErrorToErrno();
      if (!iErr) iErr = ENOENT;
    }
  }
  if (iErr) {
    switch (iErr) { /* Correct a few errors that do not map well to C library errors */
      case ESRCH: iErr = ENOTDIR; break;
      case EXDEV: iErr = 0; break;	/* End of files is NOT an error */
    }
    if (iErr) {
      errno = iErr; /* Windows' errno.h maps C-library errnos to Windows' errors */
      DEBUG_LEAVE(("return NULL; // errno=%d - %s\n", errno, strerror(errno)));
    } else {
      DEBUG_LEAVE(("return NULL; // End of directory\n"));
    }
    return NULL;
  }

  /* Set the standard fields */
  lstrcpyW((WCHAR *)(pDirent->d_name), pDir->wfd.cFileName);
  dwAttr = pDir->wfd.dwFileAttributes;
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 = pDir->wfd.dwReserved0;	/* No need to call GetReparseTag(), we got it already. */
    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 wszPath[PATH_MAX];
	WCHAR wszBuf[PATH_MAX];
	ssize_t n = lstrlenW(pDir->wszDirName);
	if ((n + 1 + lstrlenW(pDir->wfd.cFileName)) >= PATH_MAX) {
	  errno = ENAMETOOLONG; /* DIRNAME\LINKNAME won't fit in wszPath[] */
	  DEBUG_LEAVE(("return NULL; // errno=%d - %s\n", errno, strerror(errno)));
	}
	bIsMountPoint = TRUE;
	lstrcpyW(wszPath, pDir->wszDirName);
	if (n && (wszPath[n-1] != L'\\')) wszPath[n++] = L'\\';
	lstrcpyW(wszPath+n, pDir->wfd.cFileName);
	n = readlinkW(wszPath, wszBuf, COUNTOF(wszBuf));
	/* 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 */
	pDirent->d_type = DT_LNK;		/* 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)
    pDirent->d_type = DT_DIR;		/* Subdirectory */
  else if (dwAttr & FILE_ATTRIBUTE_DEVICE)
    pDirent->d_type = DT_CHR;		/* Device (we don't know if character or block) */
  else
    pDirent->d_type = DT_REG;		/* A normal file by default */

  /* Set the OS-specific extensions */
  lstrcpyW((WCHAR *)(pDirent->d_shortname), pDir->wfd.cAlternateFileName);
  pDirent->d_attribs = dwAttr;
  pDirent->d_ReparseTag = dwTag;
  pDirent->d_CreationTime = pDir->wfd.ftCreationTime;
  pDirent->d_LastAccessTime = pDir->wfd.ftLastAccessTime;
  pDirent->d_LastWriteTime = pDir->wfd.ftLastWriteTime;
  (*(ULARGE_INTEGER *)&(pDirent->d_filesize)).LowPart = pDir->wfd.nFileSizeLow;
  (*(ULARGE_INTEGER *)&(pDirent->d_filesize)).HighPart = pDir->wfd.nFileSizeHigh;

  DEBUG_WSTR2UTF8((WCHAR *)(pDirent->d_name), szUtf8, sizeof(szUtf8));
  DEBUG_LEAVE(("return 0x%p; // %s 0x%05X %10lld %s\n",
		pDirent,
		Filetime2String(&pDirent->d_LastWriteTime, szTime, sizeof(szTime)),
		(int)(pDirent->d_attribs),
		pDirent->d_filesize,
		szUtf8));
  return &(pDir->sDirent);
}

/* Read a directory entry. Return pDir, or NULL for EOF or error. MultiByte char version. */
_dirent *readdirM(DIR *pDir, UINT cp) {
  _dirent *pDirent;
  int n;
  char *pszDefaultChar;

  pDirent = readdirW(pDir);
  if (!pDirent) return NULL;

  /* Convert the name field back to MultiByte encoding */
  pszDefaultChar = (cp == CP_UTF8) ? NULL : "?";
  n = WideCharToMultiByte(cp,					/* CodePage, (CP_ACP, CP_OEMCP, CP_UTF8, ...) */
			  0,					/* dwFlags, */
			  pDir->wfd.cFileName,			/* lpWideCharStr, */
			  lstrlenW(pDir->wfd.cFileName)+1,	/* cchWideChar, */
			  pDirent->d_name,			/* lpMultiByteStr, */
			  sizeof(pDirent->d_name),		/* cbMultiByte, */
			  pszDefaultChar,			/* lpDefaultChar, */
			  NULL					/* lpUsedDefaultChar */
			  );
  if (!n) {
    errno = Win32ErrorToErrno();
    DEBUG_PRINTF(("readdirM(0x%p); // Error converting name back from Unicode. errno=%d - %s\n", pDir, errno, strerror(errno)));
    return NULL;
  }

  /* Convert the short name field back to MultiByte encoding */
  n = WideCharToMultiByte(cp,						/* CodePage, (CP_ACP, CP_OEMCP, CP_UTF8, ...) */
			  0,						/* dwFlags, */
			  pDir->wfd.cAlternateFileName,			/* lpWideCharStr, */
			  lstrlenW(pDir->wfd.cAlternateFileName)+1,	/* cchWideChar, */
			  pDirent->d_shortname,				/* lpMultiByteStr, */
			  sizeof(pDirent->d_shortname),			/* cbMultiByte, */
			  pszDefaultChar,				/* lpDefaultChar, */
			  NULL						/* lpUsedDefaultChar */
			  );
  if (!n) {
    errno = Win32ErrorToErrno();
    DEBUG_PRINTF(("readdirM(0x%p); // Error converting short name back from Unicode. errno=%d - %s\n", pDir, errno, strerror(errno)));
    return NULL;
  }

  return pDirent;
}

#endif

/*****************************************************************************\
*                                                                             *
*			      OS/2 1.x Version				      *
*                                                                             *
\*****************************************************************************/

#ifdef _OS2

/* Requires including os2.h at the beginning of this file, and before that
   defining the INCL_DOSFILEMGR constant to enable the necessary section */

int SetDirent(DIR *pDir) {	    /* Convert the FILEFINDBUF to a dirent structure */
  char *pc;
  _dirent *pDirent = &pDir->sDirent;
  FILEFINDBUF *pbuf = &pDir->buf;

  pbuf->achName[pbuf->cchName] = '\0';
  pc = strrchr(pbuf->achName, '\\'); /* Directory separator character */
  if (!pc)
    pc = &pbuf->achName[0];
  else
    pc += 1;    /* Skip the \ */

  pDirent->d_name = malloc(strlen(pc)+1);
  if (!pDirent->d_name) return ENOMEM;
  strcpy(pDirent->d_name, pc);
  pDirent->attribs = pbuf->attrFile;
  pDirent->d_type = Atype2Dtype(pDirent->attribs);
  pDirent->time = *(uint16_t *)&pbuf->ftimeLastWrite;
  pDirent->date = *(uint16_t *)&pbuf->fdateLastWrite;
  pDirent->filesize = pbuf->cbFile;

  return 0;
}

int srch1st(char *pszFile, uint8_t bAttrReq, uint8_t bAttrCmp, DIR *pDir) { /* Search first matching file */
  int n;
  int err;

  pDir->hDir = -1;

  n = 1;  /* Get one file */
  err = DosFindFirst2(pszFile, &pDir->hDir, bAttrReq & 0x7F, &pDir->buf,
		      sizeof(FILEFINDBUF), &n, FIL_STANDARD, 0L);
  if (err || !n) return 1;

  return SetDirent(pDir);
}

int srchnext(DIR *pDir) {   /* Search next matching file */
  int n;
  int err;

  n = 1;  /* Get one file */
  err = DosFindNext(pDir->hDir, &pDir->buf, sizeof(FILEFINDBUF), &n);
  if (err || !n) return 1;

  return SetDirent(&buf);
}

int srchdone(DIR *pDir) {
  int err;

  err = DosFindClose(pDir->hDir);
  pDir->hDir = -1;

  return err;
}

DIR *opendir(const char *name) { /* Open a directory */
  DIR *pDir;
  DEBUG_ENTER(("opendir(\"%s\");\n", name));
  err = _access(name, 0);
  /* To do: Get the file attributes, and check that it's a directory */
  if (err) {
    DEBUG_LEAVE(("return NULL; // errno=%d - %s\n", errno, strerror(errno)));
    return NULL;
  }
  pDir = malloc(sizeof(DIR));
  if (pDir) {
    pDir->hDir = -1;
    pDir->bAttrReq = _A_HIDDEN | _A_SYSTEM | _A_SUBDIR;
    pDir->bAttrCmp = 0;
    strcpy(pDir->sDirent.d_name, name);
  }
  DEBUG_LEAVE(("return 0x%p;\n", pDir));
  return pDir;
}

int closedir(DIR *pDir) { /* Close the directory. Return 0 if successful, -1 if not. */
  DEBUG_PRINTF(("closedir(0x%p);\n", pDir));
  if (pDir) {
    srchdone(pDir);
    free(pDir);
  }
  return 0;
}

_dirent *readdir(DIR *pDir) { /* Read a directory entry. Return pDir, or NULL for EOF or error. */
  int iErr;
  DEBUG_ENTER(("readdir(0x%p);\n", pDir));
  if (pDir->hDir == -1) {
    iErr = srch1st(pDir->sDirent.d_name, pDir->bAttrReq, pDir->bAttrCmp, pDir);
  } else {
    iErr = srchnext(pDir);
  }
  if (iErr) {
    DEBUG_LEAVE(("return NULL; // OS/2 found nothing\n",
    return NULL;
  }

  DEBUG_LEAVE(("return 0x%p; // OS/2 found: %04X %04X %02X %10lld %s\n",
		&pDir->sDirent
		(int)(pDirent->time),
		(int)(pDirent->date),
		(int)(pDirent->attribs),
		pDirent->filesize,
		pDirent->d_name));


  return &pDir->sDirent;
}

#endif

/*****************************************************************************\
*                                                                             *
*            End of OS-specific opendir/readdir/closedir versions             *
*                                                                             *
\*****************************************************************************/

/*****************************************************************************\
*                                                                             *
*   Function:	    scandir		 				      *
*									      *
*   Description:    Select entries in a directory			      *
*									      *
*   Arguments:	    const char *name	Directory name            	      *
*		    _dirent ***namelist where to store the result array       *
*		    int (*cbSelect)()   Selection callback function           *
*		    int (*cbCompare)()  Comparison function for sorting it    *
*									      *
*   Return value:   # of entries in the array, or -1 if error.		      *
*									      *
*   Notes:	    							      *
*		    							      *
*   History:								      *
*    2012-01-11 JFL Initial implementation				      *
*                                                                             *
\*****************************************************************************/

#pragma warning(disable:4706) /* Ignore the "assignment within conditional expression" warning */
int scandir(const char *pszName,
	    _dirent ***resultList,
	    int (*cbSelect) (const _dirent *),
	    int (__cdecl *cbCompare) (const _dirent **,
				      const _dirent **)) {
  int n = 0;
  DIR *pDir;
  _dirent *pDirent;
  _dirent *pDirent2;
  _dirent **pList = NULL;
  _dirent **pList2;

  DEBUG_ENTER(("scandir(\"%s\", 0x%p, 0x%p, 0x%p);\n", pszName, resultList, cbSelect, cbCompare));

  pDir = opendir(pszName);
  if (!pDir) {
    DEBUG_LEAVE(("return -1; // errno=%d\n", errno));
    return -1;
  }

  while (pDirent = readdir(pDir)) {
    if (cbSelect && !cbSelect(pDirent)) continue; /* We don't want this one. Continue search. */
    /* OK, we've selected this one. So append a copy of this _dirent to the list. */
    n += 1;
    pList2 = (_dirent **)realloc(pList, n * sizeof(_dirent *));
    pDirent2 = malloc(sizeof(_dirent));
    if (!pList2 || !pDirent2) {
      if (pDirent2) free(pDirent2);
      for (n-=1; n>0; ) free(pList[--n]);
      /* errno = ENOMEM; /* Out of memory. Should already be set by malloc failure */
      DEBUG_LEAVE(("return -1; // errno=%d\n", errno));
      return -1;
    }
    *pDirent2 = *pDirent;
    pList = pList2;
    pList[n-1] = pDirent2;
  }

  closedir(pDir);

/* 2016-09-23 JFL I don't understand why this warning still fires, so leaving it enabled for now */
#ifdef M_I86TM /* This warning appears only when compiling for the DOS tiny memory model ?!? */
/* #pragma warning(disable:4220) /* Ignore the "varargs matches remaining parameters" warning */
#endif
  if (cbCompare) qsort(pList, n, sizeof(_dirent *), cbCompare);
#ifdef M_I86TM
#pragma warning(default:4220) /* Ignore the "varargs matches remaining parameters" warning */
#endif
  *resultList = pList;
  DEBUG_LEAVE(("return %d;\n", n));
  return n;
}
#pragma warning(default:4706)

int __cdecl alphasort(const _dirent **ppDE1, const _dirent **ppDE2) {
  int ret;
  /* Sort names a-la Windows, that is case insensitive */
  ret = _strnicmp((*ppDE1)->d_name, (*ppDE2)->d_name, NAME_MAX);
  if (ret) return ret;
  /* For the remote chance that we're accessing a Unix share */
  ret = _strnicmp((*ppDE1)->d_name, (*ppDE2)->d_name, NAME_MAX);
  if (ret) return ret;
  return 0;
}

