blob: 35c0bf60e901baf3e1a634e5476c320a533aded5 [file] [log] [blame]
// Windows/FileFind.cpp
#include "StdAfx.h"
#include "FileFind.h"
#include "FileIO.h"
#include "FileName.h"
#ifndef _UNICODE
#include "../Common/StringConvert.h"
#endif
#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] != FTEXT('.'))
return false;
return Name.Len() == 1 || (Name.Len() == 2 && Name[1] == FTEXT('.'));
}
#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;
}
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 longPath;
if (GetSuperPath(path, longPath, USE_MAIN_PATH))
_handle = ::FindFirstFileW(longPath, &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 Name == L"::$DATA";
};
UString CStreamInfo::GetReducedName() const
{
UString s = Name;
if (s.Len() >= 6)
if (wcscmp(s.RightPtr(6), L":$DATA") == 0)
s.DeleteFrom(s.Len() - 6);
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;
}
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;
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 longPath;
if (GetSuperPath(path, longPath, USE_MAIN_PATH))
_handle = g_FindFirstStreamW(longPath, 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::Clear() throw()
{
Size = 0;
MY_CLEAR_FILETIME(CTime);
MY_CLEAR_FILETIME(ATime);
MY_CLEAR_FILETIME(MTime);
Attrib = 0;
IsAltStream = false;
IsDevice = false;
}
#if defined(_WIN32) && !defined(UNDER_CE)
static int FindAltStreamColon(CFSTR path)
{
for (int i = 0;; i++)
{
FChar c = path[i];
if (c == 0)
return -1;
if (c == ':')
{
if (path[i + 1] == '\\')
if (i == 1 || (i > 1 && path[i - 2] == '\\'))
{
wchar_t c0 = path[i - 1];
if (c0 >= 'a' && c0 <= 'z' ||
c0 >= 'A' && c0 <= 'Z')
continue;
}
return i;
}
}
}
#endif
bool CFileInfo::Find(CFSTR path)
{
#ifdef SUPPORT_DEVICE_FILE
if (IsDevicePath(path))
{
Clear();
Name = path + 4;
IsDevice = true;
if (/* path[0] == '\\' && path[1] == '\\' && path[2] == '.' && path[3] == '\\' && */
path[5] == ':' && 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)
{
UString streamName = fs2us(path + (unsigned)colonPos);
FString filePath = path;
filePath.DeleteFrom(colonPos);
streamName += L":$DATA"; // change it!!!!
if (Find(filePath))
{
// if (IsDir())
Attrib &= ~FILE_ATTRIBUTE_DIRECTORY;
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.IsEqualToNoCase(streamName))
{
Name += us2fs(si.Name);
Name.DeleteFrom(Name.Len() - 6);
Size = si.Size;
IsAltStream = true;
return true;
}
}
}
}
#endif
CFindFile finder;
if (finder.FindFirst(path, *this))
return true;
#ifdef _WIN32
{
DWORD lastError = GetLastError();
if (lastError == ERROR_BAD_NETPATH ||
lastError == ERROR_FILE_NOT_FOUND ||
lastError == ERROR_INVALID_NAME // for "\\SERVER\shared" paths that are translated to "\\?\UNC\SERVER\shared"
)
{
unsigned len = MyStringLen(path);
if (len > 2 && path[0] == '\\' && path[1] == '\\')
{
int startPos = 2;
if (len > kSuperUncPathPrefixSize && IsSuperUncPath(path))
startPos = kSuperUncPathPrefixSize;
int pos = FindCharPosInString(path + startPos, FTEXT('\\'));
if (pos >= 0)
{
pos += startPos + 1;
len -= pos;
int pos2 = FindCharPosInString(path + pos, FTEXT('\\'));
if (pos2 < 0 || pos2 == (int)len - 1)
{
FString s = path;
if (pos2 < 0)
{
pos2 = len;
s += FTEXT('\\');
}
s += FCHAR_ANY_MASK;
if (finder.FindFirst(s, *this))
if (Name == FTEXT("."))
{
Name.SetFrom(s.Ptr(pos), pos2);
return true;
}
::SetLastError(lastError);
}
}
}
}
}
#endif
return false;
}
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);
}
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 longPath;
if (GetSuperPath(path, longPath, USE_MAIN_PATH))
_handle = ::FindFirstChangeNotificationW(longPath, 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;
AString buf;
UINT32 newSize = GetLogicalDriveStrings(size, buf.GetBuffer(size));
if (newSize == 0 || newSize > size)
return false;
AString s;
for (UINT32 i = 0; i < newSize; i++)
{
char c = buf[i];
if (c == '\0')
{
driveStrings.Add(fas2fs(s));
s.Empty();
}
else
s += c;
}
return s.IsEmpty();
}
else
#endif
{
UINT32 size = GetLogicalDriveStringsW(0, NULL);
if (size == 0)
return false;
UString buf;
UINT32 newSize = GetLogicalDriveStringsW(size, buf.GetBuffer(size));
if (newSize == 0 || newSize > size)
return false;
UString s;
for (UINT32 i = 0; i < newSize; i++)
{
WCHAR c = buf[i];
if (c == L'\0')
{
driveStrings.Add(us2fs(s));
s.Empty();
}
else
s += c;
}
return s.IsEmpty();
}
}
#endif
}}}