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