// EnumDirItems.cpp

#include "StdAfx.h"

#include <wchar.h>

#ifdef DEBUG
#include <stdio.h>
#endif

#ifndef _WIN32
#if !defined(__WATCOMC__)
#include <grp.h>
#include <pwd.h>
#endif // !defined(__WATCOMC__)
#include "../../../Common/UTFConvert.h"
#endif

#include "../../../Common/Wildcard.h"

#if defined(__DOS__)
#include "../../../DOS/FileDir.h"
#include "../../../DOS/FileIO.h"
#include "../../../DOS/FileName.h"
using namespace NDOS;
#else
#include "../../../Windows/FileDir.h"
#include "../../../Windows/FileIO.h"
#include "../../../Windows/FileName.h"
using namespace NWindows;
#endif // defined(__DOS__)

#if defined(_WIN32) && !defined(UNDER_CE) && !defined(__WATCOMC__)
#define Z7_USE_SECURITY_CODE
#include "../../../Windows/SecurityUtils.h"
#endif

#include "EnumDirItems.h"
#include "SortUtils.h"

using namespace NFile;
using namespace NName;


static bool FindFile_KeepDots(NFile::NFind::CFileInfo &fi, const FString &path, bool followLink)
{
  const bool res = fi.Find(path, followLink);
  if (!res)
    return res;
  if (path.IsEmpty())
    return res;
  // we keep name "." and "..", if it's without tail slash
  const FChar *p = path.RightPtr(1);
  if (*p != '.')
    return res;
  if (p != path.Ptr())
  {
    FChar c = p[-1];
    if (!IS_PATH_SEPAR(c))
    {
      if (c != '.')
        return res;
      p--;
      if (p != path.Ptr())
      {
        c = p[-1];
        if (!IS_PATH_SEPAR(c))
          return res;
      }
    }
  }
  fi.Name = p;
  return res;
}


void CDirItems::AddDirFileInfo(int phyParent, int logParent, int secureIndex,
    const NFind::CFileInfo &fi)
{
  /*
  CDirItem di(fi);
  di.PhyParent = phyParent;
  di.LogParent = logParent;
  di.SecureIndex = secureIndex;
  Items.Add(di);
  */
  VECTOR_ADD_NEW_OBJECT (Items, CDirItem(fi, phyParent, logParent, secureIndex))
  
  if (fi.IsDir())
    Stat.NumDirs++;
  #if defined(_WIN32) && !defined(__WATCOMC__)
  else if (fi.IsAltStream)
  {
    Stat.NumAltStreams++;
    Stat.AltStreamsSize += fi.Size;
  }
  #endif // defined(_WIN32) && !defined(__WATCOMC__)
  else
  {
    Stat.NumFiles++;
    Stat.FilesSize += fi.Size;
  }
}

// (DWORD)E_FAIL
#define DI_DEFAULT_ERROR  ERROR_INVALID_FUNCTION

HRESULT CDirItems::AddError(const FString &path, DWORD errorCode)
{
  if (errorCode == 0)
    errorCode = DI_DEFAULT_ERROR;
  Stat.NumErrors++;
  if (Callback)
    return Callback->ScanError(path, errorCode);
  return S_OK;
}

HRESULT CDirItems::AddError(const FString &path)
{
  return AddError(path, ::GetLastError());
}

static const unsigned kScanProgressStepMask = (1 << 12) - 1;

HRESULT CDirItems::ScanProgress(const FString &dirPath)
{
  if (Callback)
    return Callback->ScanProgress(Stat, dirPath, true);
  return S_OK;
}

UString CDirItems::GetPrefixesPath(const CIntVector &parents, int index, const UString &name) const
{
  UString path;
  unsigned len = name.Len();
  
  int i;
  for (i = index; i >= 0; i = parents[(unsigned)i])
    len += Prefixes[(unsigned)i].Len();
  
  wchar_t *p = path.GetBuf_SetEnd(len) + len;
  
  p -= name.Len();
  wmemcpy(p, (const wchar_t *)name, name.Len());
  
  for (i = index; i >= 0; i = parents[(unsigned)i])
  {
    const UString &s = Prefixes[(unsigned)i];
    p -= s.Len();
    wmemcpy(p, (const wchar_t *)s, s.Len());
  }
  
  return path;
}

FString CDirItems::GetPhyPath(unsigned index) const
{
  const CDirItem &di = Items[index];
  return us2fs(GetPrefixesPath(PhyParents, di.PhyParent, di.Name));
}

UString CDirItems::GetLogPath(unsigned index) const
{
  const CDirItem &di = Items[index];
  return GetPrefixesPath(LogParents, di.LogParent, di.Name);
}

void CDirItems::ReserveDown()
{
  Prefixes.ReserveDown();
  PhyParents.ReserveDown();
  LogParents.ReserveDown();
  Items.ReserveDown();
}

unsigned CDirItems::AddPrefix(int phyParent, int logParent, const UString &prefix)
{
  PhyParents.Add(phyParent);
  LogParents.Add(logParent);
  return Prefixes.Add(prefix);
}

void CDirItems::DeleteLastPrefix()
{
  PhyParents.DeleteBack();
  LogParents.DeleteBack();
  Prefixes.DeleteBack();
}

#ifdef Z7_USE_SECURITY_CODE
bool InitLocalPrivileges();
#endif

CDirItems::CDirItems():
    SymLinks(false),
    ScanAltStreams(false)
    , ExcludeDirItems(false)
    , ExcludeFileItems(false)
    , ShareForWrite(false)
   #ifdef Z7_USE_SECURITY_CODE
    , ReadSecure(false)
   #endif
   #if !defined(_WIN32) && !defined(__DOS__)
    , StoreOwnerName(false)
   #endif
    , Callback(NULL)
{
  #ifdef Z7_USE_SECURITY_CODE
  _saclEnabled = InitLocalPrivileges();
  #endif
}


#ifdef Z7_USE_SECURITY_CODE

HRESULT CDirItems::AddSecurityItem(const FString &path, int &secureIndex)
{
  secureIndex = -1;

  SECURITY_INFORMATION securInfo =
      DACL_SECURITY_INFORMATION |
      GROUP_SECURITY_INFORMATION |
      OWNER_SECURITY_INFORMATION;
  if (_saclEnabled)
    securInfo |= SACL_SECURITY_INFORMATION;

  DWORD errorCode = 0;
  DWORD secureSize;
  
  BOOL res = ::GetFileSecurityW(fs2us(path), securInfo, (PSECURITY_DESCRIPTOR)(void *)(Byte *)TempSecureBuf, (DWORD)TempSecureBuf.Size(), &secureSize);
  
  if (res)
  {
    if (secureSize == 0)
      return S_OK;
    if (secureSize > TempSecureBuf.Size())
      errorCode = ERROR_INVALID_FUNCTION;
  }
  else
  {
    errorCode = GetLastError();
    if (errorCode == ERROR_INSUFFICIENT_BUFFER)
    {
      if (secureSize <= TempSecureBuf.Size())
        errorCode = ERROR_INVALID_FUNCTION;
      else
      {
        TempSecureBuf.Alloc(secureSize);
        res = ::GetFileSecurityW(fs2us(path), securInfo, (PSECURITY_DESCRIPTOR)(void *)(Byte *)TempSecureBuf, (DWORD)TempSecureBuf.Size(), &secureSize);
        if (res)
        {
          if (secureSize != TempSecureBuf.Size())
            errorCode = ERROR_INVALID_FUNCTION;
        }
        else
          errorCode = GetLastError();
      }
    }
  }
  
  if (res)
  {
    secureIndex = (int)SecureBlocks.AddUniq(TempSecureBuf, secureSize);
    return S_OK;
  }
  
  return AddError(path, errorCode);
}

#endif // Z7_USE_SECURITY_CODE


HRESULT CDirItems::EnumerateOneDir(const FString &phyPrefix, CObjectVector<NFind::CFileInfo> &files)
{
  NFind::CEnumerator enumerator;

  enumerator.SetDirPrefix(phyPrefix);

  #if defined(_WIN32) || defined(__DOS__)

  NFind::CFileInfo fi;

  for (unsigned ttt = 0; ; ttt++)
  {
    bool found;
    if (!enumerator.Next(fi, found))
      return AddError(phyPrefix);
    if (!found)
      return S_OK;
    files.Add(fi);
    if (Callback && (ttt & kScanProgressStepMask) == kScanProgressStepMask)
    {
      RINOK(ScanProgress(phyPrefix))
    }
  }

  #else

  // enumerator.SolveLinks = !SymLinks;
  
  CObjectVector<NFind::CDirEntry> entries;

  for (;;)
  {
    bool found;
    NFind::CDirEntry de;
    if (!enumerator.Next(de, found))
      return AddError(phyPrefix);
    if (!found)
      break;
    entries.Add(de);
  }

  FOR_VECTOR(i, entries)
  {
    const NFind::CDirEntry &de = entries[i];
    NFind::CFileInfo fi;
    if (!enumerator.Fill_FileInfo(de, fi, !SymLinks))
    // if (!fi.Find_AfterEnumerator(path))
    {
      const FString path = phyPrefix + de.Name;
      {
        RINOK(AddError(path))
        continue;
      }
    }

    files.Add(fi);

    if (Callback && (i & kScanProgressStepMask) == kScanProgressStepMask)
    {
      RINOK(ScanProgress(phyPrefix))
    }
  }

  return S_OK;

  #endif // defined(_WIN32) || defined(__DOS__)
}




HRESULT CDirItems::EnumerateDir(int phyParent, int logParent, const FString &phyPrefix)
{
  RINOK(ScanProgress(phyPrefix))

  CObjectVector<NFind::CFileInfo> files;
  RINOK(EnumerateOneDir(phyPrefix, files))

  FOR_VECTOR (i, files)
  {
    #if defined(_WIN32) || defined(__DOS__)
    const NFind::CFileInfo &fi = files[i];
    #else
    const NFind::CFileInfo &fi = files[i];
    /*
    NFind::CFileInfo fi;
    {
      const NFind::CDirEntry &di = files[i];
      const FString path = phyPrefix + di.Name;
      if (!fi.Find_AfterEnumerator(path))
      {
        RINOK(AddError(path));
        continue;
      }
      fi.Name = di.Name;
    }
    */
    #endif

    if (CanIncludeItem(fi.IsDir()))
    {
    int secureIndex = -1;
    #ifdef Z7_USE_SECURITY_CODE
    if (ReadSecure)
    {
      RINOK(AddSecurityItem(phyPrefix + fi.Name, secureIndex))
    }
    #endif
    AddDirFileInfo(phyParent, logParent, secureIndex, fi);
    }
    
    if (Callback && (i & kScanProgressStepMask) == kScanProgressStepMask)
    {
      RINOK(ScanProgress(phyPrefix))
    }

    if (fi.IsDir())
    {
      const FString name2 = fi.Name + FCHAR_PATH_SEPARATOR;
      unsigned parent = AddPrefix(phyParent, logParent, fs2us(name2));
      RINOK(EnumerateDir((int)parent, (int)parent, phyPrefix + name2))
    }
  }
  return S_OK;
}


/*
EnumerateItems2()
  const FStringVector &filePaths - are path without tail slashes.
  All dir prefixes of filePaths will be not stores in logical paths
fix it: we can scan AltStream also.
*/

#if defined(_WIN32) || defined(__DOS__)
// #define FOLLOW_LINK_PARAM
// #define FOLLOW_LINK_PARAM2
#define FOLLOW_LINK_PARAM , (!SymLinks)
#define FOLLOW_LINK_PARAM2 , (!dirItems.SymLinks)
#else
#define FOLLOW_LINK_PARAM , (!SymLinks)
#define FOLLOW_LINK_PARAM2 , (!dirItems.SymLinks)
#endif

HRESULT CDirItems::EnumerateItems2(
    const FString &phyPrefix,
    const UString &logPrefix,
    const FStringVector &filePaths,
    FStringVector *requestedPaths)
{
  const int phyParent = phyPrefix.IsEmpty() ? -1 : (int)AddPrefix(-1, -1, fs2us(phyPrefix));
  const int logParent = logPrefix.IsEmpty() ? -1 : (int)AddPrefix(-1, -1, logPrefix);

 #if defined(_WIN32) && !defined(__WATCOMC__)
  const bool phyPrefix_isAltStreamPrefix =
      NFile::NName::IsAltStreamPrefixWithColon(fs2us(phyPrefix));
 #endif // defined(_WIN32) && !defined(__WATCOMC__)

  FOR_VECTOR (i, filePaths)
  {
    const FString &filePath = filePaths[i];
    NFind::CFileInfo fi;
    const FString phyPath = phyPrefix + filePath;
    if (!FindFile_KeepDots(fi, phyPath  FOLLOW_LINK_PARAM))
    {
      RINOK(AddError(phyPath))
      continue;
    }
    if (requestedPaths)
      requestedPaths->Add(phyPath);

    const int delimiter = filePath.ReverseFind_PathSepar();
    FString phyPrefixCur;
    int phyParentCur = phyParent;
    if (delimiter >= 0)
    {
      phyPrefixCur.SetFrom(filePath, (unsigned)(delimiter + 1));
      phyParentCur = (int)AddPrefix(phyParent, logParent, fs2us(phyPrefixCur));
    }

    if (CanIncludeItem(fi.IsDir()))
    {
    int secureIndex = -1;
    #ifdef Z7_USE_SECURITY_CODE
    if (ReadSecure)
    {
      RINOK(AddSecurityItem(phyPath, secureIndex))
    }
    #endif
#if defined(_WIN32) && !defined(__WATCOMC__)
    if (phyPrefix_isAltStreamPrefix && fi.IsAltStream)
    {
      const int pos = fi.Name.Find(FChar(':'));
      if (pos >= 0)
        fi.Name.DeleteFrontal((unsigned)pos + 1);
    }
#endif // defined(_WIN32) && !defined(__WATCOMC__)
    AddDirFileInfo(phyParentCur, logParent, secureIndex, fi);
    }
    
    if (fi.IsDir())
    {
      const FString name2 = fi.Name + FCHAR_PATH_SEPARATOR;
      const unsigned parent = AddPrefix(phyParentCur, logParent, fs2us(name2));
      RINOK(EnumerateDir((int)parent, (int)parent, phyPrefix + phyPrefixCur + name2))
    }
  }
  
  ReserveDown();
  return S_OK;
}




static HRESULT EnumerateDirItems(
    const NWildcard::CCensorNode &curNode,
    const int phyParent, const int logParent,
    const FString &phyPrefix,
    const UStringVector &addParts, // additional parts from curNode
    CDirItems &dirItems,
    bool enterToSubFolders);


/* EnumerateDirItems_Spec()
   adds new Dir item prefix, and enumerates dir items,
   then it can remove that Dir item prefix, if there are no items in that dir.
*/


/*
  EnumerateDirItems_Spec()
  it's similar to EnumerateDirItems, but phyPrefix doesn't include (curFolderName)
*/

static HRESULT EnumerateDirItems_Spec(
    const NWildcard::CCensorNode &curNode,
    const int phyParent, const int logParent, const FString &curFolderName,
    const FString &phyPrefix,      // without (curFolderName)
    const UStringVector &addParts, // (curNode + addParts) includes (curFolderName)
    CDirItems &dirItems,
    bool enterToSubFolders)
{
  const FString name2 = curFolderName + FCHAR_PATH_SEPARATOR;
  const unsigned parent = dirItems.AddPrefix(phyParent, logParent, fs2us(name2));
  const unsigned numItems = dirItems.Items.Size();
  HRESULT res = EnumerateDirItems(
      curNode, (int)parent, (int)parent, phyPrefix + name2,
      addParts, dirItems, enterToSubFolders);
  if (numItems == dirItems.Items.Size())
    dirItems.DeleteLastPrefix();
  return res;
}


#if !defined(UNDER_CE) && !defined(__WATCOMC__)

#ifdef _WIN32

static HRESULT EnumerateAltStreams(
    const NFind::CFileInfo &fi,
    const NWildcard::CCensorNode &curNode,
    const int phyParent, const int logParent,
    const FString &phyPath,         // with (fi.Name), without tail slash for folders
    const UStringVector &addParts,  // with (fi.Name), prefix parts from curNode
    bool addAllSubStreams,
    CDirItems &dirItems)
{
  // we don't use (ExcludeFileItems) rules for AltStreams
  // if (dirItems.ExcludeFileItems) return S_OK;

  NFind::CStreamEnumerator enumerator(phyPath);
  for (;;)
  {
    NFind::CStreamInfo si;
    bool found;
    if (!enumerator.Next(si, found))
    {
      return dirItems.AddError(phyPath + FTEXT(":*")); // , (DWORD)E_FAIL
    }
    if (!found)
      return S_OK;
    if (si.IsMainStream())
      continue;
    UStringVector parts = addParts;
    const UString reducedName = si.GetReducedName();
    parts.Back() += reducedName;
    if (curNode.CheckPathToRoot(false, parts, true))
      continue;
    if (!addAllSubStreams)
      if (!curNode.CheckPathToRoot(true, parts, true))
        continue;

    NFind::CFileInfo fi2 = fi;
    fi2.Name += us2fs(reducedName);
    fi2.Size = si.Size;
    fi2.Attrib &= ~(DWORD)(FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT);
    fi2.IsAltStream = true;
    dirItems.AddDirFileInfo(phyParent, logParent, -1, fi2);
  }
}

#endif // _WIN32


/* We get Reparse data and parse it.
   If there is Reparse error, we free dirItem.Reparse data.
   Do we need to work with empty reparse data?
*/

HRESULT CDirItems::SetLinkInfo(CDirItem &dirItem, const NFind::CFileInfo &fi,
    const FString &phyPrefix)
{
  if (!SymLinks)
    return S_OK;

#if !defined(__WATCOMC__)
  #ifdef _WIN32
    if (!fi.HasReparsePoint() || fi.IsAltStream)
  #else // _WIN32
    if (!fi.IsPosixLink())
  #endif // _WIN32
      return S_OK;
#endif // !defined(__WATCOMC__)
  const FString path = phyPrefix + fi.Name;
  CByteBuffer &buf = dirItem.ReparseData;
  if (NIO::GetReparseData(path, buf))
  {
    // if (dirItem.ReparseData.Size() != 0)
    Stat.FilesSize -= fi.Size;
    return S_OK;
  }

  DWORD res = ::GetLastError();
  buf.Free();
  return AddError(path, res);
}

#endif // !defined(UNDER_CE) && !defined(__WATCOMC__)



static HRESULT EnumerateForItem(
    const NFind::CFileInfo &fi,
    const NWildcard::CCensorNode &curNode,
    const int phyParent, const int logParent, const FString &phyPrefix,
    const UStringVector &addParts, // additional parts from curNode, without (fi.Name)
    CDirItems &dirItems,
    bool enterToSubFolders)
{
  const UString name = fs2us(fi.Name);
  UStringVector newParts = addParts;
  newParts.Add(name);
  
  // check the path in exclude rules
  if (curNode.CheckPathToRoot(false, newParts, !fi.IsDir()))
    return S_OK;

  #if !defined(UNDER_CE)
  int dirItemIndex = -1;
  #if defined(_WIN32) && !defined(__WATCOMC__)
  bool addAllSubStreams = false;
  bool needAltStreams = true;
  #endif // defined(_WIN32) && !defined(__WATCOMC__)
  #endif // !defined(UNDER_CE)

  // check the path in inlcude rules
  if (curNode.CheckPathToRoot(true, newParts, !fi.IsDir()))
  {
    #if !defined(UNDER_CE)
    // dirItemIndex = (int)dirItems.Items.Size();
    #if defined(_WIN32) && !defined(__WATCOMC__)
    // we will not check include rules for substreams.
    addAllSubStreams = true;
    #endif // defined(_WIN32) && !defined(__WATCOMC__)
    #endif // !defined(UNDER_CE)

    if (dirItems.CanIncludeItem(fi.IsDir()))
    {
      int secureIndex = -1;
    #ifdef Z7_USE_SECURITY_CODE
      if (dirItems.ReadSecure)
      {
        RINOK(dirItems.AddSecurityItem(phyPrefix + fi.Name, secureIndex))
      }
    #endif
    #if !defined(UNDER_CE)
      dirItemIndex = (int)dirItems.Items.Size();
    #endif // !defined(UNDER_CE)
      dirItems.AddDirFileInfo(phyParent, logParent, secureIndex, fi);
    }
    else
    {
      #if defined(_WIN32) && !defined(UNDER_CE) && !defined(__WATCOMC__)
        needAltStreams = false;
      #endif
    }
    
    if (fi.IsDir())
      enterToSubFolders = true;
  }

#if !defined(UNDER_CE) && !defined(__WATCOMC__)
  
  // we don't scan AltStreams for link files

  if (dirItemIndex >= 0)
  {
    CDirItem &dirItem = dirItems.Items[(unsigned)dirItemIndex];
    RINOK(dirItems.SetLinkInfo(dirItem, fi, phyPrefix))
    if (dirItem.ReparseData.Size() != 0)
      return S_OK;
  }
  
  #if defined(_WIN32)
  if (needAltStreams && dirItems.ScanAltStreams && !fi.IsAltStream)
  {
    RINOK(EnumerateAltStreams(fi, curNode, phyParent, logParent,
        phyPrefix + fi.Name,    // with (fi.Name)
        newParts,               // with (fi.Name)
        addAllSubStreams,
        dirItems))
  }
  #endif

#endif // !defined(UNDER_CE) && !defined(__WATCOMC__)


  #if !defined(_WIN32) && !defined(__DOS__)
  if (!fi.IsPosixLink()) // posix link can follow to dir
  #endif
  if (!fi.IsDir())
    return S_OK;

  const NWildcard::CCensorNode *nextNode = NULL;

  if (addParts.IsEmpty())
  {
    int index = curNode.FindSubNode(name);
    if (index >= 0)
    {
      nextNode = &curNode.SubNodes[(unsigned)index];
      newParts.Clear();
    }
  }

  if (!nextNode)
  {
    if (!enterToSubFolders)
      return S_OK;

    #if !defined(_WIN32) && !defined(__DOS__)
    if (fi.IsPosixLink())
    {
      // here we can try to resolve posix link
      // if the link to dir, then can we follow it
      return S_OK; // we don't follow posix link
    }
    if (dirItems.SymLinks && fi.HasReparsePoint())
    {
      /* 20.03: in SymLinks mode: we don't enter to directory that
         has reparse point and has no CCensorNode
         NOTE: (curNode and parent nodes) still can have wildcard rules
         to include some items of target directory (of reparse point),
         but we ignore these rules here.
      */
      return S_OK;
    }
    #endif // !defined(_WIN32) && !defined(__DOS__)
    nextNode = &curNode;
  }
  
  return EnumerateDirItems_Spec(
      *nextNode, phyParent, logParent, fi.Name,
      phyPrefix,   // without (fi.Name)
      newParts,    // relative to (*nextNode). (*nextNode + newParts) includes (fi.Name)
      dirItems,
      enterToSubFolders);
}


static bool CanUseFsDirect(const NWildcard::CCensorNode &curNode)
{
  FOR_VECTOR (i, curNode.IncludeItems)
  {
    const NWildcard::CItem &item = curNode.IncludeItems[i];
    if (item.Recursive || item.PathParts.Size() != 1)
      return false;
    const UString &name = item.PathParts.Front();
    /*
    if (name.IsEmpty())
      return false;
    */
    
    /* Windows doesn't support file name with wildcard
       But if another system supports file name with wildcard,
       and wildcard mode is disabled, we can ignore wildcard in name
    */
    /*
    #if !defined(_WIN32) && !defined(__DOS__)
    if (!item.WildcardParsing)
      continue;
    #endif
    */
    if (DoesNameContainWildcard(name))
      return false;
  }
  return true;
}


#if defined(_WIN32) && !defined(UNDER_CE)

static bool IsVirtualFsFolder(const FString &prefix, const UString &name)
{
  UString s = fs2us(prefix);
  s += name;
  s.Add_PathSepar();
  // it returns (true) for non real FS folder path like - "\\SERVER\"
  return IsPathSepar(s[0]) && GetRootPrefixSize(s) == 0;
}

#endif



static HRESULT EnumerateDirItems(
    const NWildcard::CCensorNode &curNode,
    const int phyParent, const int logParent, const FString &phyPrefix,
    const UStringVector &addParts,  // prefix from curNode including
    CDirItems &dirItems,
    bool enterToSubFolders)
{
  if (!enterToSubFolders)
  {
    /* if there are IncludeItems censor rules that affect items in subdirs,
       then we will enter to all subfolders */
    if (curNode.NeedCheckSubDirs())
      enterToSubFolders = true;
  }
  
  RINOK(dirItems.ScanProgress(phyPrefix))

  // try direct_names case at first
  if (addParts.IsEmpty() && !enterToSubFolders)
  {
    if (CanUseFsDirect(curNode))
    {
      // all names are direct (no wildcards)
      // so we don't need file_system's dir enumerator
      CRecordVector<bool> needEnterVector;
      unsigned i;

      for (i = 0; i < curNode.IncludeItems.Size(); i++)
      {
        const NWildcard::CItem &item = curNode.IncludeItems[i];
        const UString &name = item.PathParts.Front();
        FString fullPath = phyPrefix + us2fs(name);

        /*
        // not possible now
        if (!item.ForDir && !item.ForFile)
        {
          RINOK(dirItems.AddError(fullPath, ERROR_INVALID_PARAMETER));
          continue;
        }
        */

        #if defined(_WIN32) && !defined(UNDER_CE) && !defined(__WATCOMC__)
        bool needAltStreams = true;
        #endif

        #ifdef Z7_USE_SECURITY_CODE
        bool needSecurity = true;
        #endif
        
        if (phyPrefix.IsEmpty())
        {
          if (!item.ForFile)
          {
            /* we don't like some names for alt streams inside archive:
               ":sname"     for "\"
               "c:::sname"  for "C:\"
               So we ignore alt streams for these cases */
            if (name.IsEmpty())
            {
              #if defined(_WIN32) && !defined(UNDER_CE) && !defined(__WATCOMC__)
              needAltStreams = false;
              #endif

              /*
              // do we need to ignore security info for "\\" folder ?
              #ifdef Z7_USE_SECURITY_CODE
              needSecurity = false;
              #endif
              */

              fullPath = CHAR_PATH_SEPARATOR;
            }
            #if defined(_WIN32) && !defined(UNDER_CE)
            else if (item.IsDriveItem())
            {
              needAltStreams = false;
              fullPath.Add_PathSepar();
            }
            #endif
            #if defined(__DOS__)
            else if (item.IsDriveItem())
            {
              fullPath.Add_PathSepar();
            }
            #endif // defined(__DOS__)
          }
        }

        NFind::CFileInfo fi;
        #if defined(_WIN32) && !defined(UNDER_CE)
        if (IsVirtualFsFolder(phyPrefix, name))
        {
          fi.SetAsDir();
          fi.Name = us2fs(name);
        }
        else
        #endif
        if (!FindFile_KeepDots(fi, fullPath  FOLLOW_LINK_PARAM2))
        {
          RINOK(dirItems.AddError(fullPath))
          continue;
        }

        /*
        #ifdef _WIN32
          #define MY_ERROR_IS_DIR     ERROR_FILE_NOT_FOUND
          #define MY_ERROR_NOT_DIR    DI_DEFAULT_ERROR
        #else
          #define MY_ERROR_IS_DIR     EISDIR
          #define MY_ERROR_NOT_DIR    ENOTDIR
        #endif
        */

        const bool isDir = fi.IsDir();
        if (isDir ? !item.ForDir : !item.ForFile)
        {
          // RINOK(dirItems.AddError(fullPath, isDir ? MY_ERROR_IS_DIR: MY_ERROR_NOT_DIR));
          RINOK(dirItems.AddError(fullPath, DI_DEFAULT_ERROR))
          continue;
        }
        {
          UStringVector pathParts;
          pathParts.Add(fs2us(fi.Name));
          if (curNode.CheckPathToRoot(false, pathParts, !isDir))
            continue;
        }
        

       if (dirItems.CanIncludeItem(fi.IsDir()))
       {
        int secureIndex = -1;
        #ifdef Z7_USE_SECURITY_CODE
        if (needSecurity && dirItems.ReadSecure)
        {
          RINOK(dirItems.AddSecurityItem(fullPath, secureIndex))
        }
        #endif

        dirItems.AddDirFileInfo(phyParent, logParent, secureIndex, fi);

        // we don't scan AltStreams for link files

        #if !defined(UNDER_CE) && !defined(__WATCOMC__)
        {
          CDirItem &dirItem = dirItems.Items.Back();
          RINOK(dirItems.SetLinkInfo(dirItem, fi, phyPrefix))
          if (dirItem.ReparseData.Size() != 0)
            continue;
        }
        
        #if defined(_WIN32)
        if (needAltStreams && dirItems.ScanAltStreams && !fi.IsAltStream)
        {
          UStringVector pathParts;
          pathParts.Add(fs2us(fi.Name));
          RINOK(EnumerateAltStreams(fi, curNode, phyParent, logParent,
              fullPath,  // including (name)
              pathParts, // including (fi.Name)
              true, /* addAllSubStreams */
              dirItems))
        }
        #endif // defined(_WIN32)

        #endif // !defined(UNDER_CE) && !defined(__WATCOMC__)
       }


        #if !defined(_WIN32) && !defined(__DOS__)
        if (!fi.IsPosixLink()) // posix link can follow to dir
        #endif
        if (!isDir)
          continue;

        UStringVector newParts;
        const NWildcard::CCensorNode *nextNode = NULL;
        int index = curNode.FindSubNode(name);
        if (index >= 0)
        {
          for (int t = (int)needEnterVector.Size(); t <= index; t++)
            needEnterVector.Add(true);
          needEnterVector[(unsigned)index] = false;
          nextNode = &curNode.SubNodes[(unsigned)index];
        }
        else
        {
         #if !defined(_WIN32) && !defined(__DOS__)
          if (fi.IsPosixLink())
          {
            // here we can try to resolve posix link
            // if the link to dir, then can we follow it
            continue; // we don't follow posix link
          }
          if (dirItems.SymLinks)
          {
            if (fi.HasReparsePoint())
            {
              /* 20.03: in SymLinks mode: we don't enter to directory that
              has reparse point and has no CCensorNode */
              continue;
            }
          }
         #endif // !defined(_WIN32) && !defined(__DOS__)
          nextNode = &curNode;
          newParts.Add(name); // don't change it to fi.Name. It's for shortnames support
        }

        RINOK(EnumerateDirItems_Spec(*nextNode, phyParent, logParent, fi.Name, phyPrefix,
            newParts, dirItems, true))
      }

      for (i = 0; i < curNode.SubNodes.Size(); i++)
      {
        if (i < needEnterVector.Size())
          if (!needEnterVector[i])
            continue;
        const NWildcard::CCensorNode &nextNode = curNode.SubNodes[i];
        FString fullPath = phyPrefix + us2fs(nextNode.Name);
        NFind::CFileInfo fi;
        
        if (nextNode.Name.IsEmpty())
        {
          if (phyPrefix.IsEmpty())
            fullPath = CHAR_PATH_SEPARATOR;
        }
      #ifdef _WIN32
        else if(phyPrefix.IsEmpty()
            || (phyPrefix.Len() == NName::kSuperPathPrefixSize
                && IsSuperPath(phyPrefix)))
        {
          if (NWildcard::IsDriveColonName(nextNode.Name))
            fullPath.Add_PathSepar();
        }
      #endif
      #if defined(__DOS__)
        else if(phyPrefix.IsEmpty())
        {
          if (NWildcard::IsDriveColonName(nextNode.Name))
            fullPath.Add_PathSepar();
        }
      #endif // defined(__DOS__)

        // we don't want to call fi.Find() for root folder or virtual folder
        if ((phyPrefix.IsEmpty() && nextNode.Name.IsEmpty())
            #if defined(_WIN32) && !defined(UNDER_CE)
            || IsVirtualFsFolder(phyPrefix, nextNode.Name)
            #endif
            )
        {
          fi.SetAsDir();
          fi.Name = us2fs(nextNode.Name);
        }
        else
        {
          if (!FindFile_KeepDots(fi, fullPath  FOLLOW_LINK_PARAM2))
          {
            if (!nextNode.AreThereIncludeItems())
              continue;
            RINOK(dirItems.AddError(fullPath))
            continue;
          }
        
          if (!fi.IsDir())
          {
            RINOK(dirItems.AddError(fullPath, DI_DEFAULT_ERROR))
            continue;
          }
        }

        RINOK(EnumerateDirItems_Spec(nextNode, phyParent, logParent, fi.Name, phyPrefix,
            UStringVector(), dirItems, false))
      }

      return S_OK;
    }
  }

  #ifdef _WIN32
  #ifndef UNDER_CE

  // scan drives, if wildcard is "*:\"

  if (phyPrefix.IsEmpty() && curNode.IncludeItems.Size() > 0)
  {
    unsigned i;
    for (i = 0; i < curNode.IncludeItems.Size(); i++)
    {
      const NWildcard::CItem &item = curNode.IncludeItems[i];
      if (item.PathParts.Size() < 1)
        break;
      const UString &name = item.PathParts.Front();
      if (name.Len() != 2 || name[1] != ':')
        break;
      if (item.PathParts.Size() == 1)
        if (item.ForFile || !item.ForDir)
          break;
      if (NWildcard::IsDriveColonName(name))
        continue;
      if (name[0] != '*' && name[0] != '?')
        break;
    }
    if (i == curNode.IncludeItems.Size())
    {
      FStringVector driveStrings;
      NFind::MyGetLogicalDriveStrings(driveStrings);
      for (i = 0; i < driveStrings.Size(); i++)
      {
        FString driveName = driveStrings[i];
        if (driveName.Len() < 3 || driveName.Back() != '\\')
          return E_FAIL;
        driveName.DeleteBack();
        NFind::CFileInfo fi;
        fi.SetAsDir();
        fi.Name = driveName;

        RINOK(EnumerateForItem(fi, curNode, phyParent, logParent, phyPrefix,
            addParts, dirItems, enterToSubFolders))
      }
      return S_OK;
    }
  }
  
  #endif
  #endif


  CObjectVector<NFind::CFileInfo> files;
  
  // for (int y = 0; y < 1; y++)
  {
    // files.Clear();
    RINOK(dirItems.EnumerateOneDir(phyPrefix, files))
  /*
  FOR_VECTOR (i, files)
  {
    #if defined(_WIN32) || defined(__DOS__)
    // const NFind::CFileInfo &fi = files[i];
    #else
    NFind::CFileInfo &fi = files[i];
    {
      const NFind::CFileInfo &di = files[i];
      const FString path = phyPrefix + di.Name;
      if (!fi.Find_AfterEnumerator(path))
      {
        RINOK(dirItems.AddError(path));
        continue;
      }
      fi.Name = di.Name;
    }
    #endif

  }
  */
  }

  FOR_VECTOR (i, files)
  {
    #if defined(_WIN32) || defined(__DOS__)
    const NFind::CFileInfo &fi = files[i];
    #else
    const NFind::CFileInfo &fi = files[i];
    /*
    NFind::CFileInfo fi;
    {
      const NFind::CDirEntry &di = files[i];
      const FString path = phyPrefix + di.Name;
      if (!fi.Find_AfterEnumerator(path))
      {
        RINOK(dirItems.AddError(path));
        continue;
      }
      fi.Name = di.Name;
    }
    */
    #endif



    RINOK(EnumerateForItem(fi, curNode, phyParent, logParent, phyPrefix,
          addParts, dirItems, enterToSubFolders))
    if (dirItems.Callback && (i & kScanProgressStepMask) == kScanProgressStepMask)
    {
      RINOK(dirItems.ScanProgress(phyPrefix))
    }
  }

  return S_OK;
}




HRESULT EnumerateItems(
    const NWildcard::CCensor &censor,
    const NWildcard::ECensorPathMode pathMode,
    const UString &addPathPrefix, // prefix that will be added to Logical Path
    CDirItems &dirItems)
{
  FOR_VECTOR (i, censor.Pairs)
  {
    const NWildcard::CPair &pair = censor.Pairs[i];
    const int phyParent = pair.Prefix.IsEmpty() ? -1 : (int)dirItems.AddPrefix(-1, -1, pair.Prefix);
    int logParent = -1;
    
    if (pathMode == NWildcard::k_AbsPath)
      logParent = phyParent;
    else
    {
      if (!addPathPrefix.IsEmpty())
        logParent = (int)dirItems.AddPrefix(-1, -1, addPathPrefix);
    }
    
    RINOK(EnumerateDirItems(pair.Head, phyParent, logParent, us2fs(pair.Prefix), UStringVector(),
        dirItems,
        false // enterToSubFolders
        ))
  }
  dirItems.ReserveDown();

 #if defined(_WIN32) && !defined(UNDER_CE) && !defined(__WATCOMC__)
  RINOK(dirItems.FillFixedReparse())
 #endif

 #if !defined(_WIN32) && !defined(__DOS__)
  RINOK(dirItems.FillDeviceSizes())
 #endif

  return S_OK;
}


#if defined(_WIN32) && !defined(UNDER_CE) && !defined(__WATCOMC__)

HRESULT CDirItems::FillFixedReparse()
{
  FOR_VECTOR(i, Items)
  {
    CDirItem &item = Items[i];

    if (!SymLinks)
    {
      // continue; // for debug
      if (!item.Has_Attrib_ReparsePoint())
        continue;

      // if (item.IsDir()) continue;

      const FString phyPath = GetPhyPath(i);

      NFind::CFileInfo fi;
      if (fi.Fill_From_ByHandleFileInfo(phyPath)) // item.IsDir()
      {
        item.Size = fi.Size;
        item.CTime = fi.CTime;
        item.ATime = fi.ATime;
        item.MTime = fi.MTime;
        item.Attrib = fi.Attrib;
        continue;
      }

      /*
      // we request properties of target file instead of properies of symbolic link
      // here we also can manually parse unsupported links (like WSL links)
      NIO::CInFile inFile;
      if (inFile.Open(phyPath))
      {
        BY_HANDLE_FILE_INFORMATION info;
        if (inFile.GetFileInformation(&info))
        {
          // Stat.FilesSize doesn't contain item.Size already
          // Stat.FilesSize -= item.Size;
          item.Size = (((UInt64)info.nFileSizeHigh) << 32) + info.nFileSizeLow;
          Stat.FilesSize += item.Size;
          item.CTime = info.ftCreationTime;
          item.ATime = info.ftLastAccessTime;
          item.MTime = info.ftLastWriteTime;
          item.Attrib = info.dwFileAttributes;
          continue;
        }
      }
      */

      RINOK(AddError(phyPath))
      continue;
    }

    // (SymLinks == true) here

    if (item.ReparseData.Size() == 0)
      continue;

    // if (item.Size == 0)
    {
      // 20.03: we use Reparse Data instead of real data
      item.Size = item.ReparseData.Size();
    }
    
    CReparseAttr attr;
    if (!attr.Parse(item.ReparseData, item.ReparseData.Size()))
    {
      const FString phyPath = GetPhyPath(i);
      AddError(phyPath, attr.ErrorCode);
      continue;
    }

    /* imagex/WIM reduces absolute paths in links (raparse data),
       if we archive non root folder. We do same thing here */

    bool isWSL = false;
    if (attr.IsSymLink_WSL())
    {
      // isWSL = true;
      // we don't change WSL symlinks
      continue;
    }
    else
    {
      if (attr.IsRelative_Win())
        continue;
    }

    const UString &link = attr.GetPath();
    if (!IsDrivePath(link))
      continue;
    // maybe we need to support networks paths also ?

    FString fullPathF;
    if (!NDir::MyGetFullPathName(GetPhyPath(i), fullPathF))
      continue;
    const UString fullPath = fs2us(fullPathF);
    const UString logPath = GetLogPath(i);
    if (logPath.Len() >= fullPath.Len())
      continue;
    if (CompareFileNames(logPath, fullPath.RightPtr(logPath.Len())) != 0)
      continue;
    
    const UString prefix = fullPath.Left(fullPath.Len() - logPath.Len());
    if (!IsPathSepar(prefix.Back()))
      continue;

    const unsigned rootPrefixSize = GetRootPrefixSize(prefix);
    if (rootPrefixSize == 0)
      continue;
    if (rootPrefixSize == prefix.Len())
      continue; // simple case: paths are from root

    if (link.Len() <= prefix.Len())
      continue;

    if (CompareFileNames(link.Left(prefix.Len()), prefix) != 0)
      continue;

    UString newLink = prefix.Left(rootPrefixSize);
    newLink += link.Ptr(prefix.Len());

    CByteBuffer data;
    bool isSymLink = !attr.IsMountPoint();
    if (!FillLinkData(data, newLink, isSymLink, isWSL))
      continue;
    item.ReparseData2 = data;
  }
  return S_OK;
}

#endif // defined(_WIN32) && !defined(UNDER_CE) && !defined(__WATCOMC__)


#if !defined(_WIN32) && !defined(__DOS__)

HRESULT CDirItems::FillDeviceSizes()
{
  {
    FOR_VECTOR (i, Items)
    {
      CDirItem &item = Items[i];
      
      if (S_ISBLK(item.mode) && item.Size == 0)
      {
        const FString phyPath = GetPhyPath(i);
        NIO::CInFile inFile;
        inFile.PreserveATime = true;
        if (inFile.OpenShared(phyPath, ShareForWrite)) // fixme: OpenShared ??
        {
          UInt64 size = 0;
          if (inFile.GetLength(size))
            item.Size = size;
        }
      }
#if !defined(__WATCOMC__)
      if (StoreOwnerName)
      {
        OwnerNameMap.Add_UInt32(item.uid);
        OwnerGroupMap.Add_UInt32(item.gid);
      }
#endif // !defined(__WATCOMC__)
    }
  }

#if !defined(__WATCOMC__)
  if (StoreOwnerName)
  {
    UString u;
    AString a;
    {
      FOR_VECTOR (i, OwnerNameMap.Numbers)
      {
        // 200K/sec speed
        u.Empty();
        const passwd *pw = getpwuid(OwnerNameMap.Numbers[i]);
        // printf("\ngetpwuid=%s\n", pw->pw_name);
        if (pw)
        {
          a = pw->pw_name;
          ConvertUTF8ToUnicode(a, u);
        }
        OwnerNameMap.Strings.Add(u);
      }
    }
    {
      FOR_VECTOR (i, OwnerGroupMap.Numbers)
      {
        u.Empty();
        const group *gr = getgrgid(OwnerGroupMap.Numbers[i]);
        if (gr)
        {
          // printf("\ngetgrgid %d %s\n", OwnerGroupMap.Numbers[i], gr->gr_name);
          a = gr->gr_name;
          ConvertUTF8ToUnicode(a, u);
        }
        OwnerGroupMap.Strings.Add(u);
      }
    }
    
    FOR_VECTOR (i, Items)
    {
      CDirItem &item = Items[i];
      {
        const int index = OwnerNameMap.Find(item.uid);
        if (index < 0) throw 1;
        item.OwnerNameIndex = index;
      }
      {
        const int index = OwnerGroupMap.Find(item.gid);
        if (index < 0) throw 1;
        item.OwnerGroupIndex = index;
      }
    }
  }
      

  // if (NeedOwnerNames)
  {
    /*
    {
      for (unsigned i = 0 ; i < 10000; i++)
      {
        const passwd *pw = getpwuid(i);
        if (pw)
        {
          UString u;
          ConvertUTF8ToUnicode(AString(pw->pw_name), u);
          OwnerNameMap.Add(i, u);
          OwnerNameMap.Add(i, u);
          OwnerNameMap.Add(i, u);
        }
        const group *gr = getgrgid(i);
        if (gr)
        {
          // we can use utf-8 here.
          UString u;
          ConvertUTF8ToUnicode(AString(gr->gr_name), u);
          OwnerGroupMap.Add(i, u);
        }
      }
    }
    */
    /*
    {
      FOR_VECTOR (i, OwnerNameMap.Strings)
      {
        AString s;
        ConvertUnicodeToUTF8(OwnerNameMap.Strings[i], s);
        printf("\n%5d %s", (unsigned)OwnerNameMap.Numbers[i], s.Ptr());
      }
    }
    {
      printf("\n\n=========Groups\n");
      FOR_VECTOR (i, OwnerGroupMap.Strings)
      {
        AString s;
        ConvertUnicodeToUTF8(OwnerGroupMap.Strings[i], s);
        printf("\n%5d %s", (unsigned)OwnerGroupMap.Numbers[i], s.Ptr());
      }
    }
    */
  }
#endif // !defined(__WATCOMC__)
      /*
      for (unsigned i = 0 ; i < 100000000; i++)
      {
        // const passwd *pw = getpwuid(1000);
        // pw = pw;
        int pos = OwnerNameMap.Find(1000);
        if (pos < 0 - (int)i)
          throw 1;
      }
      */

  return S_OK;
}

#endif



static const char * const kCannotFindArchive = "Cannot find archive";

HRESULT EnumerateDirItemsAndSort(
    NWildcard::CCensor &censor,
    NWildcard::ECensorPathMode censorPathMode,
    const UString &addPathPrefix,
    UStringVector &sortedPaths,
    UStringVector &sortedFullPaths,
    CDirItemsStat &st,
    IDirItemsCallback *callback)
{
  FStringVector paths;
  
  {
    CDirItems dirItems;
    dirItems.Callback = callback;
    {
      HRESULT res = EnumerateItems(censor, censorPathMode, addPathPrefix, dirItems);
      st = dirItems.Stat;
      RINOK(res)
    }
  
    FOR_VECTOR (i, dirItems.Items)
    {
      const CDirItem &dirItem = dirItems.Items[i];
      if (!dirItem.IsDir())
        paths.Add(dirItems.GetPhyPath(i));
    }
  }
  
  if (paths.Size() == 0)
  {
    // return S_OK;
    throw CMessagePathException(kCannotFindArchive);
  }
  
  UStringVector fullPaths;
  
  unsigned i;
  
  for (i = 0; i < paths.Size(); i++)
  {
    FString fullPath;
    NFile::NDir::MyGetFullPathName(paths[i], fullPath);
    fullPaths.Add(fs2us(fullPath));
  }
  
  CUIntVector indices;
  SortFileNames(fullPaths, indices);
  sortedPaths.ClearAndReserve(indices.Size());
  sortedFullPaths.ClearAndReserve(indices.Size());

  for (i = 0; i < indices.Size(); i++)
  {
    unsigned index = indices[i];
    sortedPaths.AddInReserved(fs2us(paths[index]));
    sortedFullPaths.AddInReserved(fullPaths[index]);
    if (i > 0 && CompareFileNames(sortedFullPaths[i], sortedFullPaths[i - 1]) == 0)
      throw CMessagePathException("Duplicate archive path:", sortedFullPaths[i]);
  }

  return S_OK;
}




#if defined(_WIN32) || defined(__DOS__)

static bool IsDotsName(const wchar_t *s)
{
  return s[0] == '.' && (s[1] == 0 || (s[1] == '.' && s[2] == 0));
}

// This code converts all short file names to long file names.

static void ConvertToLongName(const UString &prefix, UString &name)
{
  if (name.IsEmpty()
      || DoesNameContainWildcard(name)
      || IsDotsName(name))
    return;
  NFind::CFileInfo fi;
  const FString path (us2fs(prefix + name));
  #if !defined(UNDER_CE) && !defined(__DOS__)
  if (NFile::NName::IsDevicePath(path))
    return;
  #endif
  if (fi.Find(path))
    name = fs2us(fi.Name);
}

static void ConvertToLongNames(const UString &prefix, CObjectVector<NWildcard::CItem> &items)
{
  FOR_VECTOR (i, items)
  {
    NWildcard::CItem &item = items[i];
    if (item.Recursive || item.PathParts.Size() != 1)
      continue;
    if (prefix.IsEmpty() && item.IsDriveItem())
      continue;
    ConvertToLongName(prefix, item.PathParts.Front());
  }
}

static void ConvertToLongNames(const UString &prefix, NWildcard::CCensorNode &node)
{
  ConvertToLongNames(prefix, node.IncludeItems);
  ConvertToLongNames(prefix, node.ExcludeItems);
  unsigned i;
  for (i = 0; i < node.SubNodes.Size(); i++)
  {
    UString &name = node.SubNodes[i].Name;
    if (prefix.IsEmpty() && NWildcard::IsDriveColonName(name))
      continue;
    ConvertToLongName(prefix, name);
  }
  // mix folders with same name
  for (i = 0; i < node.SubNodes.Size(); i++)
  {
    NWildcard::CCensorNode &nextNode1 = node.SubNodes[i];
    for (unsigned j = i + 1; j < node.SubNodes.Size();)
    {
      const NWildcard::CCensorNode &nextNode2 = node.SubNodes[j];
      if (nextNode1.Name.IsEqualTo_NoCase(nextNode2.Name))
      {
        nextNode1.IncludeItems += nextNode2.IncludeItems;
        nextNode1.ExcludeItems += nextNode2.ExcludeItems;
        node.SubNodes.Delete(j);
      }
      else
        j++;
    }
  }
  for (i = 0; i < node.SubNodes.Size(); i++)
  {
    NWildcard::CCensorNode &nextNode = node.SubNodes[i];
    ConvertToLongNames(prefix + nextNode.Name + WCHAR_PATH_SEPARATOR, nextNode);
  }
}

void ConvertToLongNames(NWildcard::CCensor &censor)
{
  FOR_VECTOR (i, censor.Pairs)
  {
    NWildcard::CPair &pair = censor.Pairs[i];
    ConvertToLongNames(pair.Prefix, pair.Head);
  }
}

#endif // defined(_WIN32) || defined(__DOS__)


CMessagePathException::CMessagePathException(const char *a, const wchar_t *u)
{
  (*this) += a;
  if (u)
  {
    Add_LF();
    (*this) += u;
  }
}

CMessagePathException::CMessagePathException(const wchar_t *a, const wchar_t *u)
{
  (*this) += a;
  if (u)
  {
    Add_LF();
    (*this) += u;
  }
}
