| // 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 | |
| }}} |