// EnumDirItems.cpp
#include "StdAfx.h"
#include "../../../Common/Wildcard.h"
#include "../../../Windows/FileDir.h"
#include "../../../Windows/FileIO.h"
#include "../../../Windows/FileName.h"
#if defined(_WIN32) && !defined(UNDER_CE)
#define _USE_SECURITY_CODE
#include "../../../Windows/SecurityUtils.h"
#endif
#include "EnumDirItems.h"
using namespace NWindows;
using namespace NFile;
using namespace NName;
void AddDirFileInfo(int phyParent, int logParent, int secureIndex,
const NFind::CFileInfo &fi, CObjectVector<CDirItem> &dirItems)
{
CDirItem di;
di.Size = fi.Size;
di.CTime = fi.CTime;
di.ATime = fi.ATime;
di.MTime = fi.MTime;
di.Attrib = fi.Attrib;
di.IsAltStream = fi.IsAltStream;
di.PhyParent = phyParent;
di.LogParent = logParent;
di.SecureIndex = secureIndex;
di.Name = fs2us(fi.Name);
#if defined(_WIN32) && !defined(UNDER_CE)
// di.ShortName = fs2us(fi.ShortName);
#endif
dirItems.Add(di);
}
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[i])
len += Prefixes[i].Len();
unsigned totalLen = len;
wchar_t *p = path.GetBuffer(len);
p[len] = 0;
len -= name.Len();
memcpy(p + len, (const wchar_t *)name, name.Len() * sizeof(wchar_t));
for (i = index; i >= 0; i = parents[i])
{
const UString &s = Prefixes[i];
len -= s.Len();
memcpy(p + len, (const wchar_t *)s, s.Len() * sizeof(wchar_t));
}
path.ReleaseBuffer(totalLen);
return path;
}
UString CDirItems::GetPhyPath(unsigned index) const
{
const CDirItem &di = Items[index];
return 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();
}
bool InitLocalPrivileges();
CDirItems::CDirItems():
SymLinks(false),
TotalSize(0)
#ifdef _USE_SECURITY_CODE
, ReadSecure(false)
#endif
{
#ifdef _USE_SECURITY_CODE
_saclEnabled = InitLocalPrivileges();
#endif
}
#ifdef _USE_SECURITY_CODE
void 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)(Byte *)TempSecureBuf, (DWORD)TempSecureBuf.Size(), &secureSize);
if (res)
{
if (secureSize == 0)
return;
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)(Byte *)TempSecureBuf, (DWORD)TempSecureBuf.Size(), &secureSize);
if (res)
{
if (secureSize != TempSecureBuf.Size())
errorCode = ERROR_INVALID_FUNCTION;;
}
else
errorCode = GetLastError();
}
}
}
if (res)
{
secureIndex = SecureBlocks.AddUniq(TempSecureBuf, secureSize);
return;
}
if (errorCode == 0)
errorCode = ERROR_INVALID_FUNCTION;
AddError(path, errorCode);
}
#endif
void CDirItems::EnumerateDir(int phyParent, int logParent, const FString &phyPrefix)
{
NFind::CEnumerator enumerator(phyPrefix + FCHAR_ANY_MASK);
for (;;)
{
NFind::CFileInfo fi;
bool found;
if (!enumerator.Next(fi, found))
{
AddError(phyPrefix);
return;
}
if (!found)
break;
int secureIndex = -1;
#ifdef _USE_SECURITY_CODE
if (ReadSecure)
AddSecurityItem(phyPrefix + fi.Name, secureIndex);
#endif
AddDirFileInfo(phyParent, logParent, secureIndex, fi, Items);
if (fi.IsDir())
{
const FString name2 = fi.Name + FCHAR_PATH_SEPARATOR;
unsigned parent = AddPrefix(phyParent, logParent, fs2us(name2));
EnumerateDir(parent, parent, phyPrefix + name2);
}
}
}
void CDirItems::EnumerateItems2(
const FString &phyPrefix,
const UString &logPrefix,
const FStringVector &filePaths,
FStringVector *requestedPaths)
{
int phyParent = phyPrefix.IsEmpty() ? -1 : AddPrefix(-1, -1, fs2us(phyPrefix));
int logParent = logPrefix.IsEmpty() ? -1 : AddPrefix(-1, -1, logPrefix);
FOR_VECTOR (i, filePaths)
{
const FString &filePath = filePaths[i];
NFind::CFileInfo fi;
const FString phyPath = phyPrefix + filePath;
if (!fi.Find(phyPath))
{
AddError(phyPath);
continue;
}
if (requestedPaths)
requestedPaths->Add(phyPath);
int delimiter = filePath.ReverseFind(FCHAR_PATH_SEPARATOR);
FString phyPrefixCur;
int phyParentCur = phyParent;
if (delimiter >= 0)
{
phyPrefixCur.SetFrom(filePath, delimiter + 1);
phyParentCur = AddPrefix(phyParent, logParent, fs2us(phyPrefixCur));
}
int secureIndex = -1;
#ifdef _USE_SECURITY_CODE
if (ReadSecure)
AddSecurityItem(phyPath, secureIndex);
#endif
AddDirFileInfo(phyParentCur, logParent, secureIndex, fi, Items);
if (fi.IsDir())
{
const FString name2 = fi.Name + FCHAR_PATH_SEPARATOR;
unsigned parent = AddPrefix(phyParentCur, logParent, fs2us(name2));
EnumerateDir(parent, parent, phyPrefix + phyPrefixCur + name2);
}
}
ReserveDown();
}
static HRESULT EnumerateDirItems(
const NWildcard::CCensorNode &curNode,
int phyParent, int logParent, const FString &phyPrefix,
const UStringVector &addArchivePrefix,
CDirItems &dirItems,
bool enterToSubFolders,
IEnumDirItemCallback *callback);
static HRESULT EnumerateDirItems_Spec(
const NWildcard::CCensorNode &curNode,
int phyParent, int logParent, const FString &curFolderName,
const FString &phyPrefix,
const UStringVector &addArchivePrefix,
CDirItems &dirItems,
bool enterToSubFolders,
IEnumDirItemCallback *callback)
{
const FString name2 = curFolderName + FCHAR_PATH_SEPARATOR;
unsigned parent = dirItems.AddPrefix(phyParent, logParent, fs2us(name2));
unsigned numItems = dirItems.Items.Size();
HRESULT res = EnumerateDirItems(
curNode, parent, parent, phyPrefix + name2,
addArchivePrefix, dirItems, enterToSubFolders, callback);
if (numItems == dirItems.Items.Size())
dirItems.DeleteLastPrefix();
return res;
}
#ifndef UNDER_CE
static void EnumerateAltStreams(
const NFind::CFileInfo &fi,
const NWildcard::CCensorNode &curNode,
int phyParent, int logParent, const FString &phyPrefix,
const UStringVector &addArchivePrefix, // prefix from curNode
CDirItems &dirItems)
{
const FString fullPath = phyPrefix + fi.Name;
NFind::CStreamEnumerator enumerator(fullPath);
for (;;)
{
NFind::CStreamInfo si;
bool found;
if (!enumerator.Next(si, found))
{
dirItems.AddError(fullPath + FTEXT(":*"), (DWORD)E_FAIL);
break;
}
if (!found)
break;
if (si.IsMainStream())
continue;
UStringVector addArchivePrefixNew = addArchivePrefix;
UString reducedName = si.GetReducedName();
addArchivePrefixNew.Back() += reducedName;
if (curNode.CheckPathToRoot(false, addArchivePrefixNew, true))
continue;
NFind::CFileInfo fi2 = fi;
fi2.Name += us2fs(reducedName);
fi2.Size = si.Size;
fi2.Attrib &= ~FILE_ATTRIBUTE_DIRECTORY;
fi2.IsAltStream = true;
AddDirFileInfo(phyParent, logParent, -1, fi2, dirItems.Items);
dirItems.TotalSize += fi2.Size;
}
}
void CDirItems::SetLinkInfo(CDirItem &dirItem, const NFind::CFileInfo &fi,
const FString &phyPrefix)
{
if (!SymLinks || !fi.HasReparsePoint())
return;
const FString path = phyPrefix + fi.Name;
CByteBuffer &buf = dirItem.ReparseData;
if (NIO::GetReparseData(path, buf))
{
CReparseAttr attr;
if (attr.Parse(buf, buf.Size()))
return;
}
AddError(path);
buf.Free();
}
#endif
static HRESULT EnumerateForItem(
NFind::CFileInfo &fi,
const NWildcard::CCensorNode &curNode,
int phyParent, int logParent, const FString &phyPrefix,
const UStringVector &addArchivePrefix, // prefix from curNode
CDirItems &dirItems,
bool enterToSubFolders,
IEnumDirItemCallback *callback)
{
const UString name = fs2us(fi.Name);
bool enterToSubFolders2 = enterToSubFolders;
UStringVector addArchivePrefixNew = addArchivePrefix;
addArchivePrefixNew.Add(name);
{
UStringVector addArchivePrefixNewTemp(addArchivePrefixNew);
if (curNode.CheckPathToRoot(false, addArchivePrefixNewTemp, !fi.IsDir()))
return S_OK;
}
int dirItemIndex = -1;
if (curNode.CheckPathToRoot(true, addArchivePrefixNew, !fi.IsDir()))
{
int secureIndex = -1;
#ifdef _USE_SECURITY_CODE
if (dirItems.ReadSecure)
dirItems.AddSecurityItem(phyPrefix + fi.Name, secureIndex);
#endif
dirItemIndex = dirItems.Items.Size();
AddDirFileInfo(phyParent, logParent, secureIndex, fi, dirItems.Items);
dirItems.TotalSize += fi.Size;
if (fi.IsDir())
enterToSubFolders2 = true;
}
#ifndef UNDER_CE
if (dirItems.ScanAltStreams)
{
EnumerateAltStreams(fi, curNode, phyParent, logParent, phyPrefix,
addArchivePrefixNew, dirItems);
}
if (dirItemIndex >= 0)
{
CDirItem &dirItem = dirItems.Items[dirItemIndex];
dirItems.SetLinkInfo(dirItem, fi, phyPrefix);
if (dirItem.ReparseData.Size() != 0)
return S_OK;
}
#endif
if (!fi.IsDir())
return S_OK;
const NWildcard::CCensorNode *nextNode = 0;
if (addArchivePrefix.IsEmpty())
{
int index = curNode.FindSubNode(name);
if (index >= 0)
nextNode = &curNode.SubNodes[index];
}
if (!enterToSubFolders2 && nextNode == 0)
return S_OK;
addArchivePrefixNew = addArchivePrefix;
if (nextNode == 0)
{
nextNode = &curNode;
addArchivePrefixNew.Add(name);
}
return EnumerateDirItems_Spec(
*nextNode, phyParent, logParent, fi.Name, phyPrefix,
addArchivePrefixNew,
dirItems,
enterToSubFolders2, callback);
}
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 (!item.WildcardParsing)
continue;
*/
if (DoesNameContainWildcard(name))
return false;
}
return true;
}
static HRESULT EnumerateDirItems(
const NWildcard::CCensorNode &curNode,
int phyParent, int logParent, const FString &phyPrefix,
const UStringVector &addArchivePrefix, // prefix from curNode
CDirItems &dirItems,
bool enterToSubFolders,
IEnumDirItemCallback *callback)
{
if (!enterToSubFolders)
if (curNode.NeedCheckSubDirs())
enterToSubFolders = true;
if (callback)
RINOK(callback->ScanProgress(dirItems.GetNumFolders(), dirItems.Items.Size(), dirItems.TotalSize, fs2us(phyPrefix), true));
// try direct_names case at first
if (addArchivePrefix.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();
const FString fullPath = phyPrefix + us2fs(name);
NFind::CFileInfo fi;
#ifdef _WIN32
if (phyPrefix.IsEmpty() && item.IsDriveItem())
{
fi.SetAsDir();
fi.Name = fullPath;
}
else
#endif
if (!fi.Find(fullPath))
{
dirItems.AddError(fullPath);
continue;
}
bool isDir = fi.IsDir();
if (isDir && !item.ForDir || !isDir && !item.ForFile)
{
dirItems.AddError(fullPath, (DWORD)E_FAIL);
continue;
}
{
UStringVector pathParts;
pathParts.Add(fs2us(fi.Name));
if (curNode.CheckPathToRoot(false, pathParts, !isDir))
continue;
}
int secureIndex = -1;
#ifdef _USE_SECURITY_CODE
if (dirItems.ReadSecure)
dirItems.AddSecurityItem(fullPath, secureIndex);
#endif
AddDirFileInfo(phyParent, logParent, secureIndex, fi, dirItems.Items);
#ifndef UNDER_CE
{
CDirItem &dirItem = dirItems.Items.Back();
dirItems.SetLinkInfo(dirItem, fi, phyPrefix);
if (dirItem.ReparseData.Size() != 0)
continue;
}
#endif
dirItems.TotalSize += fi.Size;
#ifndef UNDER_CE
if (dirItems.ScanAltStreams)
{
UStringVector pathParts;
pathParts.Add(fs2us(fi.Name));
EnumerateAltStreams(fi, curNode, phyParent, logParent, phyPrefix,
pathParts, dirItems);
}
#endif
if (!isDir)
continue;
UStringVector addArchivePrefixNew;
const NWildcard::CCensorNode *nextNode = 0;
int index = curNode.FindSubNode(name);
if (index >= 0)
{
for (int t = needEnterVector.Size(); t <= index; t++)
needEnterVector.Add(true);
needEnterVector[index] = false;
nextNode = &curNode.SubNodes[index];
}
else
{
nextNode = &curNode;
addArchivePrefixNew.Add(name); // don't change it to fi.Name. It's for shortnames support
}
RINOK(EnumerateDirItems_Spec(*nextNode, phyParent, logParent, fi.Name, phyPrefix,
addArchivePrefixNew, dirItems, true, callback));
}
for (i = 0; i < curNode.SubNodes.Size(); i++)
{
if (i < needEnterVector.Size())
if (!needEnterVector[i])
continue;
const NWildcard::CCensorNode &nextNode = curNode.SubNodes[i];
const FString fullPath = phyPrefix + us2fs(nextNode.Name);
NFind::CFileInfo fi;
#ifdef _WIN32
if (phyPrefix.IsEmpty() && NWildcard::IsDriveColonName(nextNode.Name))
{
fi.SetAsDir();
fi.Name = fullPath;
}
else
#endif
if (!fi.Find(fullPath))
{
if (!nextNode.AreThereIncludeItems())
continue;
dirItems.AddError(fullPath);
continue;
}
if (!fi.IsDir())
{
dirItems.AddError(fullPath, (DWORD)E_FAIL);
continue;
}
RINOK(EnumerateDirItems_Spec(nextNode, phyParent, logParent, fi.Name, phyPrefix,
UStringVector(), dirItems, false, callback));
}
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,
addArchivePrefix, dirItems, enterToSubFolders, callback));
}
return S_OK;
}
}
#endif
#endif
NFind::CEnumerator enumerator(phyPrefix + FCHAR_ANY_MASK);
for (unsigned ttt = 0; ; ttt++)
{
NFind::CFileInfo fi;
bool found;
if (!enumerator.Next(fi, found))
{
dirItems.AddError(phyPrefix);
break;
}
if (!found)
break;
if (callback && (ttt & 0xFF) == 0xFF)
RINOK(callback->ScanProgress(dirItems.GetNumFolders(), dirItems.Items.Size(), dirItems.TotalSize, fs2us(phyPrefix), true));
RINOK(EnumerateForItem(fi, curNode, phyParent, logParent, phyPrefix,
addArchivePrefix, dirItems, enterToSubFolders, callback));
}
return S_OK;
}
HRESULT EnumerateItems(
const NWildcard::CCensor &censor,
const NWildcard::ECensorPathMode pathMode,
const UString &addPathPrefix,
CDirItems &dirItems,
IEnumDirItemCallback *callback)
{
FOR_VECTOR (i, censor.Pairs)
{
const NWildcard::CPair &pair = censor.Pairs[i];
int phyParent = pair.Prefix.IsEmpty() ? -1 : dirItems.AddPrefix(-1, -1, pair.Prefix);
int logParent = -1;
if (pathMode == NWildcard::k_AbsPath)
logParent = phyParent;
else
{
if (!addPathPrefix.IsEmpty())
logParent = dirItems.AddPrefix(-1, -1, addPathPrefix);
}
RINOK(EnumerateDirItems(pair.Head, phyParent, logParent, us2fs(pair.Prefix), UStringVector(),
dirItems,
false, // enterToSubFolders
callback));
}
dirItems.ReserveDown();
#if defined(_WIN32) && !defined(UNDER_CE)
dirItems.FillFixedReparse();
#endif
return S_OK;
}
#if defined(_WIN32) && !defined(UNDER_CE)
void CDirItems::FillFixedReparse()
{
/* imagex/WIM reduces absolute pathes in links (raparse data),
if we archive non root folder. We do same thing here */
if (!SymLinks)
return;
FOR_VECTOR(i, Items)
{
CDirItem &item = Items[i];
if (item.ReparseData.Size() == 0)
continue;
CReparseAttr attr;
if (!attr.Parse(item.ReparseData, item.ReparseData.Size()))
continue;
if (attr.IsRelative())
continue;
const UString &link = attr.GetPath();
if (!IsDrivePath(link))
continue;
// maybe we need to support networks paths also ?
FString fullPathF;
if (!NDir::MyGetFullPathName(us2fs(GetPhyPath(i)), fullPathF))
continue;
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 (prefix.Back() != WCHAR_PATH_SEPARATOR)
continue;
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;
if (!FillLinkData(data, newLink, attr.IsSymLink()))
continue;
item.ReparseData2 = data;
}
}
#endif