Source code

Revision control

Copy as Markdown

Other Tools

// Windows/FileFind.cpp↩
#include "StdAfx.h"
#ifndef _UNICODE↩
#include "../Common/StringConvert.h"
#endif
#include "FileFind.h"
#include "FileIO.h"
#include "FileName.h"
#ifndef _UNICODE↩
extern bool g_IsNT;↩
#endif
using namespace NWindows;↩
using namespace NFile;↩
using namespace NName;↩
#if defined(_WIN32) && !defined(UNDER_CE)↩
EXTERN_C_BEGIN↩
typedef enum
{↩
My_FindStreamInfoStandard,↩
My_FindStreamInfoMaxInfoLevel↩
} MY_STREAM_INFO_LEVELS;↩
typedef struct
{↩
LARGE_INTEGER StreamSize;↩
WCHAR cStreamName[MAX_PATH + 36];↩
} MY_WIN32_FIND_STREAM_DATA, *MY_PWIN32_FIND_STREAM_DATA;↩
typedef WINBASEAPI HANDLE (WINAPI *FindFirstStreamW_Ptr)(LPCWSTR fileName, MY_STREAM_INFO_LEVELS infoLevel,↩
LPVOID findStreamData, DWORD flags);↩
typedef WINBASEAPI BOOL (APIENTRY *FindNextStreamW_Ptr)(HANDLE findStream, LPVOID findStreamData);↩
EXTERN_C_END↩
#endif
namespace NWindows {↩
namespace NFile {↩
#ifdef SUPPORT_DEVICE_FILE↩
namespace NSystem↩
{↩
bool MyGetDiskFreeSpace(CFSTR rootPath, UInt64 &clusterSize, UInt64 &totalSize, UInt64 &freeSize);↩
}↩
#endif
namespace NFind {↩
bool CFileInfo::IsDots() const throw()↩
{↩
if (!IsDir() || Name.IsEmpty())↩
return false;↩
if (Name[0] != '.')↩
return false;↩
return Name.Len() == 1 || (Name.Len() == 2 && Name[1] == '.');↩
}↩
#define WIN_FD_TO_MY_FI(fi, fd) \↩
fi.Attrib = fd.dwFileAttributes; \↩
fi.CTime = fd.ftCreationTime; \↩
fi.ATime = fd.ftLastAccessTime; \↩
fi.MTime = fd.ftLastWriteTime; \↩
fi.Size = (((UInt64)fd.nFileSizeHigh) << 32) + fd.nFileSizeLow; \↩
fi.IsAltStream = false; \↩
fi.IsDevice = false;↩
/*↩
#ifdef UNDER_CE↩
fi.ObjectID = fd.dwOID;↩
#else↩
fi.ReparseTag = fd.dwReserved0;↩
#endif↩
*/
static void Convert_WIN32_FIND_DATA_to_FileInfo(const WIN32_FIND_DATAW &fd, CFileInfo &fi)↩
{↩
WIN_FD_TO_MY_FI(fi, fd);↩
fi.Name = us2fs(fd.cFileName);↩
#if defined(_WIN32) && !defined(UNDER_CE)↩
// fi.ShortName = us2fs(fd.cAlternateFileName);↩
#endif
}↩
#ifndef _UNICODE↩
static void Convert_WIN32_FIND_DATA_to_FileInfo(const WIN32_FIND_DATA &fd, CFileInfo &fi)↩
{↩
WIN_FD_TO_MY_FI(fi, fd);↩
fi.Name = fas2fs(fd.cFileName);↩
#if defined(_WIN32) && !defined(UNDER_CE)↩
// fi.ShortName = fas2fs(fd.cAlternateFileName);↩
#endif
}↩
#endif
////////////////////////////////↩
// CFindFile↩
bool CFindFileBase::Close() throw()↩
{↩
if (_handle == INVALID_HANDLE_VALUE)↩
return true;↩
if (!::FindClose(_handle))↩
return false;↩
_handle = INVALID_HANDLE_VALUE;↩
return true;↩
}↩
/*↩
WinXP-64 FindFirstFile():↩
"" - ERROR_PATH_NOT_FOUND↩
folder\ - ERROR_FILE_NOT_FOUND↩
\ - ERROR_FILE_NOT_FOUND↩
c:\ - ERROR_FILE_NOT_FOUND↩
c: - ERROR_FILE_NOT_FOUND, if current dir is ROOT ( c:\ )↩
c: - OK, if current dir is NOT ROOT ( c:\folder )↩
folder - OK↩
\\ - ERROR_INVALID_NAME↩
\\Server - ERROR_INVALID_NAME↩
\\Server\ - ERROR_INVALID_NAME↩
\\Server\Share - ERROR_BAD_NETPATH↩
\\Server\Share - ERROR_BAD_NET_NAME (Win7).↩
!!! There is problem : Win7 makes some requests for "\\Server\Shar" (look in Procmon),↩
when we call it for "\\Server\Share"↩
\\Server\Share\ - ERROR_FILE_NOT_FOUND↩
\\?\UNC\Server\Share - ERROR_INVALID_NAME↩
\\?\UNC\Server\Share - ERROR_BAD_PATHNAME (Win7)↩
\\?\UNC\Server\Share\ - ERROR_FILE_NOT_FOUND↩
\\Server\Share_RootDrive - ERROR_INVALID_NAME↩
\\Server\Share_RootDrive\ - ERROR_INVALID_NAME↩
c:\* - ERROR_FILE_NOT_FOUND, if thare are no item in that folder↩
*/
bool CFindFile::FindFirst(CFSTR path, CFileInfo &fi)↩
{↩
if (!Close())↩
return false;↩
#ifndef _UNICODE↩
if (!g_IsNT)↩
{↩
WIN32_FIND_DATAA fd;↩
_handle = ::FindFirstFileA(fs2fas(path), &fd);↩
if (_handle == INVALID_HANDLE_VALUE)↩
return false;↩
Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi);↩
}↩
else
#endif
{↩
WIN32_FIND_DATAW fd;↩
IF_USE_MAIN_PATH↩
_handle = ::FindFirstFileW(fs2us(path), &fd);↩
#ifdef WIN_LONG_PATH↩
if (_handle == INVALID_HANDLE_VALUE && USE_SUPER_PATH)↩
{↩
UString superPath;↩
if (GetSuperPath(path, superPath, USE_MAIN_PATH))↩
_handle = ::FindFirstFileW(superPath, &fd);↩
}↩
#endif
if (_handle == INVALID_HANDLE_VALUE)↩
return false;↩
Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi);↩
}↩
return true;↩
}↩
bool CFindFile::FindNext(CFileInfo &fi)↩
{↩
#ifndef _UNICODE↩
if (!g_IsNT)↩
{↩
WIN32_FIND_DATAA fd;↩
if (!::FindNextFileA(_handle, &fd))↩
return false;↩
Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi);↩
}↩
else
#endif
{↩
WIN32_FIND_DATAW fd;↩
if (!::FindNextFileW(_handle, &fd))↩
return false;↩
Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi);↩
}↩
return true;↩
}↩
#if defined(_WIN32) && !defined(UNDER_CE)↩
////////////////////////////////↩
// AltStreams↩
static FindFirstStreamW_Ptr g_FindFirstStreamW;↩
static FindNextStreamW_Ptr g_FindNextStreamW;↩
struct CFindStreamLoader↩
{↩
CFindStreamLoader()↩
{↩
g_FindFirstStreamW = (FindFirstStreamW_Ptr)::GetProcAddress(::GetModuleHandleA("kernel32.dll"), "FindFirstStreamW");↩
g_FindNextStreamW = (FindNextStreamW_Ptr)::GetProcAddress(::GetModuleHandleA("kernel32.dll"), "FindNextStreamW");↩
}↩
} g_FindStreamLoader;↩
bool CStreamInfo::IsMainStream() const throw()↩
{↩
return StringsAreEqualNoCase_Ascii(Name, "::$DATA");↩
};↩
UString CStreamInfo::GetReducedName() const
{↩
// remove ":$DATA" postfix, but keep postfix, if Name is "::$DATA"↩
UString s (Name);↩
if (s.Len() > 6 + 1 && StringsAreEqualNoCase_Ascii(s.RightPtr(6), ":$DATA"))↩
s.DeleteFrom(s.Len() - 6);↩
return s;↩
}↩
/*↩
UString CStreamInfo::GetReducedName2() const↩
{↩
UString s = GetReducedName();↩
if (!s.IsEmpty() && s[0] == ':')↩
s.Delete(0);↩
return s;↩
}↩
*/
static void Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(const MY_WIN32_FIND_STREAM_DATA &sd, CStreamInfo &si)↩
{↩
si.Size = sd.StreamSize.QuadPart;↩
si.Name = sd.cStreamName;↩
}↩
/*↩
WinXP-64 FindFirstStream():↩
"" - ERROR_PATH_NOT_FOUND↩
folder\ - OK↩
folder - OK↩
\ - OK↩
c:\ - OK↩
c: - OK, if current dir is ROOT ( c:\ )↩
c: - OK, if current dir is NOT ROOT ( c:\folder )↩
\\Server\Share - OK↩
\\Server\Share\ - OK↩
\\ - ERROR_INVALID_NAME↩
\\Server - ERROR_INVALID_NAME↩
\\Server\ - ERROR_INVALID_NAME↩
*/
bool CFindStream::FindFirst(CFSTR path, CStreamInfo &si)↩
{↩
if (!Close())↩
return false;↩
if (!g_FindFirstStreamW)↩
{↩
::SetLastError(ERROR_CALL_NOT_IMPLEMENTED);↩
return false;↩
}↩
{↩
MY_WIN32_FIND_STREAM_DATA sd;↩
SetLastError(0);↩
IF_USE_MAIN_PATH↩
_handle = g_FindFirstStreamW(fs2us(path), My_FindStreamInfoStandard, &sd, 0);↩
if (_handle == INVALID_HANDLE_VALUE)↩
{↩
if (::GetLastError() == ERROR_HANDLE_EOF)↩
return false;↩
// long name can be tricky for path like ".\dirName".↩
#ifdef WIN_LONG_PATH↩
if (USE_SUPER_PATH)↩
{↩
UString superPath;↩
if (GetSuperPath(path, superPath, USE_MAIN_PATH))↩
_handle = g_FindFirstStreamW(superPath, My_FindStreamInfoStandard, &sd, 0);↩
}↩
#endif
}↩
if (_handle == INVALID_HANDLE_VALUE)↩
return false;↩
Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(sd, si);↩
}↩
return true;↩
}↩
bool CFindStream::FindNext(CStreamInfo &si)↩
{↩
if (!g_FindNextStreamW)↩
{↩
::SetLastError(ERROR_CALL_NOT_IMPLEMENTED);↩
return false;↩
}↩
{↩
MY_WIN32_FIND_STREAM_DATA sd;↩
if (!g_FindNextStreamW(_handle, &sd))↩
return false;↩
Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(sd, si);↩
}↩
return true;↩
}↩
bool CStreamEnumerator::Next(CStreamInfo &si, bool &found)↩
{↩
bool res;↩
if (_find.IsHandleAllocated())↩
res = _find.FindNext(si);↩
else
res = _find.FindFirst(_filePath, si);↩
if (res)↩
{↩
found = true;↩
return true;↩
}↩
found = false;↩
return (::GetLastError() == ERROR_HANDLE_EOF);↩
}↩
#endif
#define MY_CLEAR_FILETIME(ft) ft.dwLowDateTime = ft.dwHighDateTime = 0;↩
void CFileInfoBase::ClearBase() throw()↩
{↩
Size = 0;↩
MY_CLEAR_FILETIME(CTime);↩
MY_CLEAR_FILETIME(ATime);↩
MY_CLEAR_FILETIME(MTime);↩
Attrib = 0;↩
IsAltStream = false;↩
IsDevice = false;↩
}↩
/*↩
WinXP-64 GetFileAttributes():↩
If the function fails, it returns INVALID_FILE_ATTRIBUTES and use GetLastError() to get error code↩
\ - OK↩
C:\ - OK, if there is such drive,↩
D:\ - ERROR_PATH_NOT_FOUND, if there is no such drive,↩
C:\folder - OK↩
C:\folder\ - OK↩
C:\folderBad - ERROR_FILE_NOT_FOUND↩
\\Server\BadShare - ERROR_BAD_NETPATH↩
\\Server\Share - WORKS OK, but MSDN says:↩
GetFileAttributes for a network share, the function fails, and GetLastError↩
returns ERROR_BAD_NETPATH. You must specify a path to a subfolder on that share.↩
*/
DWORD GetFileAttrib(CFSTR path)↩
{↩
#ifndef _UNICODE↩
if (!g_IsNT)↩
return ::GetFileAttributes(fs2fas(path));↩
else
#endif
{↩
IF_USE_MAIN_PATH↩
{↩
DWORD dw = ::GetFileAttributesW(fs2us(path));↩
if (dw != INVALID_FILE_ATTRIBUTES)↩
return dw;↩
}↩
#ifdef WIN_LONG_PATH↩
if (USE_SUPER_PATH)↩
{↩
UString superPath;↩
if (GetSuperPath(path, superPath, USE_MAIN_PATH))↩
return ::GetFileAttributesW(superPath);↩
}↩
#endif
return INVALID_FILE_ATTRIBUTES;↩
}↩
}↩
/* if path is "c:" or "c::" then CFileInfo::Find() returns name of current folder for that disk↩
so instead of absolute path we have relative path in Name. That is not good in some calls */
/* In CFileInfo::Find() we want to support same names for alt streams as in CreateFile(). */
/* CFileInfo::Find()↩
We alow the following paths (as FindFirstFile):↩
C:\folder↩
c: - if current dir is NOT ROOT ( c:\folder )↩
also we support paths that are not supported by FindFirstFile:↩
\↩
\\.\c:↩
c:\ - Name will be without tail slash ( c: )↩
\\?\c:\ - Name will be without tail slash ( c: )↩
\\Server\Share↩
\\?\UNC\Server\Share↩
c:\folder:stream - Name = folder:stream↩
c:\:stream - Name = :stream↩
c::stream - Name = c::stream↩
*/
bool CFileInfo::Find(CFSTR path)↩
{↩
#ifdef SUPPORT_DEVICE_FILE↩
if (IsDevicePath(path))↩
{↩
ClearBase();↩
Name = path + 4;↩
IsDevice = true;↩
if (NName::IsDrivePath2(path + 4) && path[6] == 0)↩
{↩
FChar drive[4] = { path[4], ':', '\\', 0 };↩
UInt64 clusterSize, totalSize, freeSize;↩
if (NSystem::MyGetDiskFreeSpace(drive, clusterSize, totalSize, freeSize))↩
{↩
Size = totalSize;↩
return true;↩
}↩
}↩
NIO::CInFile inFile;↩
// ::OutputDebugStringW(path);↩
if (!inFile.Open(path))↩
return false;↩
// ::OutputDebugStringW(L"---");↩
if (inFile.SizeDefined)↩
Size = inFile.Size;↩
return true;↩
}↩
#endif
#if defined(_WIN32) && !defined(UNDER_CE)↩
int colonPos = FindAltStreamColon(path);↩
if (colonPos >= 0 && path[(unsigned)colonPos + 1] != 0)↩
{↩
UString streamName = fs2us(path + (unsigned)colonPos);↩
FString filePath (path);↩
filePath.DeleteFrom(colonPos);↩
/* we allow both cases:↩
name:stream↩
name:stream:$DATA↩
*/
const unsigned kPostfixSize = 6;↩
if (streamName.Len() <= kPostfixSize↩
|| !StringsAreEqualNoCase_Ascii(streamName.RightPtr(kPostfixSize), ":$DATA"))↩
streamName += ":$DATA";↩
bool isOk = true;↩
if (IsDrivePath2(filePath) &&↩
(colonPos == 2 || colonPos == 3 && filePath[2] == '\\'))↩
{↩
// FindFirstFile doesn't work for "c:\" and for "c:" (if current dir is ROOT)↩
ClearBase();↩
Name.Empty();↩
if (colonPos == 2)↩
Name = filePath;↩
}↩
else
isOk = Find(filePath);↩
if (isOk)↩
{↩
Attrib &= ~(FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT);↩
Size = 0;↩
CStreamEnumerator enumerator(filePath);↩
for (;;)↩
{↩
CStreamInfo si;↩
bool found;↩
if (!enumerator.Next(si, found))↩
return false;↩
if (!found)↩
{↩
::SetLastError(ERROR_FILE_NOT_FOUND);↩
return false;↩
}↩
if (si.Name.IsEqualTo_NoCase(streamName))↩
{↩
// we delete postfix, if alt stream name is not "::$DATA"↩
if (si.Name.Len() > kPostfixSize + 1)↩
si.Name.DeleteFrom(si.Name.Len() - kPostfixSize);↩
Name += us2fs(si.Name);↩
Size = si.Size;↩
IsAltStream = true;↩
return true;↩
}↩
}↩
}↩
}↩
#endif
CFindFile finder;↩
#if defined(_WIN32) && !defined(UNDER_CE)↩
{↩
/*↩
DWORD lastError = GetLastError();↩
if (lastError == ERROR_FILE_NOT_FOUND↩
|| lastError == ERROR_BAD_NETPATH // XP64: "\\Server\Share"↩
|| lastError == ERROR_BAD_NET_NAME // Win7: "\\Server\Share"↩
|| lastError == ERROR_INVALID_NAME // XP64: "\\?\UNC\Server\Share"↩
|| lastError == ERROR_BAD_PATHNAME // Win7: "\\?\UNC\Server\Share"↩
)↩
*/
unsigned rootSize = 0;↩
if (IsSuperPath(path))↩
rootSize = kSuperPathPrefixSize;↩
if (NName::IsDrivePath(path + rootSize) && path[rootSize + 3] == 0)↩
{↩
DWORD attrib = GetFileAttrib(path);↩
if (attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) != 0)↩
{↩
ClearBase();↩
Attrib = attrib;↩
Name = path + rootSize;↩
Name.DeleteFrom(2); // we don't need backslash (C:)↩
return true;↩
}↩
}↩
else if (IS_PATH_SEPAR(path[0]))↩
if (path[1] == 0)↩
{↩
DWORD attrib = GetFileAttrib(path);↩
if (attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) != 0)↩
{↩
ClearBase();↩
Name.Empty();↩
Attrib = attrib;↩
return true;↩
}↩
}↩
else
{↩
const unsigned prefixSize = GetNetworkServerPrefixSize(path);↩
if (prefixSize > 0 && path[prefixSize] != 0)↩
{↩
if (NName::FindSepar(path + prefixSize) < 0)↩
{↩
FString s (path);↩
s.Add_PathSepar();↩
s += '*'; // CHAR_ANY_MASK↩
bool isOK = false;↩
if (finder.FindFirst(s, *this))↩
{↩
if (Name == FTEXT("."))↩
{↩
Name = path + prefixSize;↩
return true;↩
}↩
isOK = true;↩
/* if "\\server\share" maps to root folder "d:\", there is no "." item.↩
But it's possible that there are another items */
}↩
{↩
DWORD attrib = GetFileAttrib(path);↩
if (isOK || attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) != 0)↩
{↩
ClearBase();↩
if (attrib != INVALID_FILE_ATTRIBUTES)↩
Attrib = attrib;↩
else
SetAsDir();↩
Name = path + prefixSize;↩
return true;↩
}↩
}↩
// ::SetLastError(lastError);↩
}↩
}↩
}↩
}↩
#endif
return finder.FindFirst(path, *this);↩
}↩
bool DoesFileExist(CFSTR name)↩
{↩
CFileInfo fi;↩
return fi.Find(name) && !fi.IsDir();↩
}↩
bool DoesDirExist(CFSTR name)↩
{↩
CFileInfo fi;↩
return fi.Find(name) && fi.IsDir();↩
}↩
bool DoesFileOrDirExist(CFSTR name)↩
{↩
CFileInfo fi;↩
return fi.Find(name);↩
}↩
void CEnumerator::SetDirPrefix(const FString &dirPrefix)↩
{↩
_wildcard = dirPrefix;↩
_wildcard += '*';↩
}↩
bool CEnumerator::NextAny(CFileInfo &fi)↩
{↩
if (_findFile.IsHandleAllocated())↩
return _findFile.FindNext(fi);↩
else
return _findFile.FindFirst(_wildcard, fi);↩
}↩
bool CEnumerator::Next(CFileInfo &fi)↩
{↩
for (;;)↩
{↩
if (!NextAny(fi))↩
return false;↩
if (!fi.IsDots())↩
return true;↩
}↩
}↩
bool CEnumerator::Next(CFileInfo &fi, bool &found)↩
{↩
if (Next(fi))↩
{↩
found = true;↩
return true;↩
}↩
found = false;↩
return (::GetLastError() == ERROR_NO_MORE_FILES);↩
}↩
////////////////////////////////↩
// CFindChangeNotification↩
// FindFirstChangeNotification can return 0. MSDN doesn't tell about it.↩
bool CFindChangeNotification::Close() throw()↩
{↩
if (!IsHandleAllocated())↩
return true;↩
if (!::FindCloseChangeNotification(_handle))↩
return false;↩
_handle = INVALID_HANDLE_VALUE;↩
return true;↩
}↩
HANDLE CFindChangeNotification::FindFirst(CFSTR path, bool watchSubtree, DWORD notifyFilter)↩
{↩
#ifndef _UNICODE↩
if (!g_IsNT)↩
_handle = ::FindFirstChangeNotification(fs2fas(path), BoolToBOOL(watchSubtree), notifyFilter);↩
else
#endif
{↩
IF_USE_MAIN_PATH↩
_handle = ::FindFirstChangeNotificationW(fs2us(path), BoolToBOOL(watchSubtree), notifyFilter);↩
#ifdef WIN_LONG_PATH↩
if (!IsHandleAllocated())↩
{↩
UString superPath;↩
if (GetSuperPath(path, superPath, USE_MAIN_PATH))↩
_handle = ::FindFirstChangeNotificationW(superPath, BoolToBOOL(watchSubtree), notifyFilter);↩
}↩
#endif
}↩
return _handle;↩
}↩
#ifndef UNDER_CE↩
bool MyGetLogicalDriveStrings(CObjectVector<FString> &driveStrings)↩
{↩
driveStrings.Clear();↩
#ifndef _UNICODE↩
if (!g_IsNT)↩
{↩
driveStrings.Clear();↩
UINT32 size = GetLogicalDriveStrings(0, NULL);↩
if (size == 0)↩
return false;↩
CObjArray<char> buf(size);↩
UINT32 newSize = GetLogicalDriveStrings(size, buf);↩
if (newSize == 0 || newSize > size)↩
return false;↩
AString s;↩
UINT32 prev = 0;↩
for (UINT32 i = 0; i < newSize; i++)↩
{↩
if (buf[i] == 0)↩
{↩
s = buf + prev;↩
prev = i + 1;↩
driveStrings.Add(fas2fs(s));↩
}↩
}↩
return prev == newSize;↩
}↩
else
#endif
{↩
UINT32 size = GetLogicalDriveStringsW(0, NULL);↩
if (size == 0)↩
return false;↩
CObjArray<wchar_t> buf(size);↩
UINT32 newSize = GetLogicalDriveStringsW(size, buf);↩
if (newSize == 0 || newSize > size)↩
return false;↩
UString s;↩
UINT32 prev = 0;↩
for (UINT32 i = 0; i < newSize; i++)↩
{↩
if (buf[i] == 0)↩
{↩
s = buf + prev;↩
prev = i + 1;↩
driveStrings.Add(us2fs(s));↩
}↩
}↩
return prev == newSize;↩
}↩
}↩
#endif
}}}↩