// Windows/FileDir.cpp #include "StdAfx.h" #ifndef _UNICODE #include "../Common/StringConvert.h" #endif #include "FileDir.h" #include "FileFind.h" #include "FileName.h" #ifndef _UNICODE extern bool g_IsNT; #endif using namespace NWindows; using namespace NFile; using namespace NName; namespace NWindows { namespace NFile { namespace NDir { #ifndef UNDER_CE bool GetWindowsDir(FString &path) { UINT needLength; #ifndef _UNICODE if (!g_IsNT) { TCHAR s[MAX_PATH + 2]; s[0] = 0; needLength = ::GetWindowsDirectory(s, MAX_PATH + 1); path = fas2fs(s); } else #endif { WCHAR s[MAX_PATH + 2]; s[0] = 0; needLength = ::GetWindowsDirectoryW(s, MAX_PATH + 1); path = us2fs(s); } return (needLength > 0 && needLength <= MAX_PATH); } bool GetSystemDir(FString &path) { UINT needLength; #ifndef _UNICODE if (!g_IsNT) { TCHAR s[MAX_PATH + 2]; s[0] = 0; needLength = ::GetSystemDirectory(s, MAX_PATH + 1); path = fas2fs(s); } else #endif { WCHAR s[MAX_PATH + 2]; s[0] = 0; needLength = ::GetSystemDirectoryW(s, MAX_PATH + 1); path = us2fs(s); } return (needLength > 0 && needLength <= MAX_PATH); } #endif bool SetDirTime(CFSTR path, const FILETIME *cTime, const FILETIME *aTime, const FILETIME *mTime) { #ifndef _UNICODE if (!g_IsNT) { ::SetLastError(ERROR_CALL_NOT_IMPLEMENTED); return false; } #endif HANDLE hDir = INVALID_HANDLE_VALUE; IF_USE_MAIN_PATH hDir = ::CreateFileW(fs2us(path), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); #ifdef WIN_LONG_PATH if (hDir == INVALID_HANDLE_VALUE && USE_SUPER_PATH) { UString superPath; if (GetSuperPath(path, superPath, USE_MAIN_PATH)) hDir = ::CreateFileW(superPath, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); } #endif bool res = false; if (hDir != INVALID_HANDLE_VALUE) { res = BOOLToBool(::SetFileTime(hDir, cTime, aTime, mTime)); ::CloseHandle(hDir); } return res; } bool SetFileAttrib(CFSTR path, DWORD attrib) { #ifndef _UNICODE if (!g_IsNT) { if (::SetFileAttributes(fs2fas(path), attrib)) return true; } else #endif { IF_USE_MAIN_PATH if (::SetFileAttributesW(fs2us(path), attrib)) return true; #ifdef WIN_LONG_PATH if (USE_SUPER_PATH) { UString superPath; if (GetSuperPath(path, superPath, USE_MAIN_PATH)) return BOOLToBool(::SetFileAttributesW(superPath, attrib)); } #endif } return false; } bool SetFileAttrib_PosixHighDetect(CFSTR path, DWORD attrib) { if ((attrib & 0xF0000000) != 0) attrib &= 0x3FFF; return SetFileAttrib(path, attrib); } bool RemoveDir(CFSTR path) { #ifndef _UNICODE if (!g_IsNT) { if (::RemoveDirectory(fs2fas(path))) return true; } else #endif { IF_USE_MAIN_PATH if (::RemoveDirectoryW(fs2us(path))) return true; #ifdef WIN_LONG_PATH if (USE_SUPER_PATH) { UString superPath; if (GetSuperPath(path, superPath, USE_MAIN_PATH)) return BOOLToBool(::RemoveDirectoryW(superPath)); } #endif } return false; } bool MyMoveFile(CFSTR oldFile, CFSTR newFile) { #ifndef _UNICODE if (!g_IsNT) { if (::MoveFile(fs2fas(oldFile), fs2fas(newFile))) return true; } else #endif { IF_USE_MAIN_PATH_2(oldFile, newFile) if (::MoveFileW(fs2us(oldFile), fs2us(newFile))) return true; #ifdef WIN_LONG_PATH if (USE_SUPER_PATH_2) { UString d1, d2; if (GetSuperPaths(oldFile, newFile, d1, d2, USE_MAIN_PATH_2)) return BOOLToBool(::MoveFileW(d1, d2)); } #endif } return false; } #ifndef UNDER_CE EXTERN_C_BEGIN typedef BOOL (WINAPI *Func_CreateHardLinkW)( LPCWSTR lpFileName, LPCWSTR lpExistingFileName, LPSECURITY_ATTRIBUTES lpSecurityAttributes ); EXTERN_C_END bool MyCreateHardLink(CFSTR newFileName, CFSTR existFileName) { #ifndef _UNICODE if (!g_IsNT) { SetLastError(ERROR_CALL_NOT_IMPLEMENTED); return false; /* if (::CreateHardLink(fs2fas(newFileName), fs2fas(existFileName), NULL)) return true; */ } else #endif { Func_CreateHardLinkW my_CreateHardLinkW = (Func_CreateHardLinkW) ::GetProcAddress(::GetModuleHandleW(L"kernel32.dll"), "CreateHardLinkW"); if (!my_CreateHardLinkW) return false; IF_USE_MAIN_PATH_2(newFileName, existFileName) if (my_CreateHardLinkW(fs2us(newFileName), fs2us(existFileName), NULL)) return true; #ifdef WIN_LONG_PATH if (USE_SUPER_PATH_2) { UString d1, d2; if (GetSuperPaths(newFileName, existFileName, d1, d2, USE_MAIN_PATH_2)) return BOOLToBool(my_CreateHardLinkW(d1, d2, NULL)); } #endif } return false; } #endif /* WinXP-64 CreateDir(): "" - ERROR_PATH_NOT_FOUND \ - ERROR_ACCESS_DENIED C:\ - ERROR_ACCESS_DENIED, if there is such drive, D:\folder - ERROR_PATH_NOT_FOUND, if there is no such drive, C:\nonExistent\folder - ERROR_PATH_NOT_FOUND C:\existFolder - ERROR_ALREADY_EXISTS C:\existFolder\ - ERROR_ALREADY_EXISTS C:\folder - OK C:\folder\ - OK \\Server\nonExistent - ERROR_BAD_NETPATH \\Server\Share_Readonly - ERROR_ACCESS_DENIED \\Server\Share - ERROR_ALREADY_EXISTS \\Server\Share_NTFS_drive - ERROR_ACCESS_DENIED \\Server\Share_FAT_drive - ERROR_ALREADY_EXISTS */ bool CreateDir(CFSTR path) { #ifndef _UNICODE if (!g_IsNT) { if (::CreateDirectory(fs2fas(path), NULL)) return true; } else #endif { IF_USE_MAIN_PATH if (::CreateDirectoryW(fs2us(path), NULL)) return true; #ifdef WIN_LONG_PATH if ((!USE_MAIN_PATH || ::GetLastError() != ERROR_ALREADY_EXISTS) && USE_SUPER_PATH) { UString superPath; if (GetSuperPath(path, superPath, USE_MAIN_PATH)) return BOOLToBool(::CreateDirectoryW(superPath, NULL)); } #endif } return false; } /* CreateDir2 returns true, if directory can contain files after the call (two cases): 1) the directory already exists 2) the directory was created path must be WITHOUT trailing path separator. We need CreateDir2, since fileInfo.Find() for reserved names like "com8" returns FILE instead of DIRECTORY. And we need to use SuperPath */ static bool CreateDir2(CFSTR path) { #ifndef _UNICODE if (!g_IsNT) { if (::CreateDirectory(fs2fas(path), NULL)) return true; } else #endif { IF_USE_MAIN_PATH if (::CreateDirectoryW(fs2us(path), NULL)) return true; #ifdef WIN_LONG_PATH if ((!USE_MAIN_PATH || ::GetLastError() != ERROR_ALREADY_EXISTS) && USE_SUPER_PATH) { UString superPath; if (GetSuperPath(path, superPath, USE_MAIN_PATH)) { if (::CreateDirectoryW(superPath, NULL)) return true; if (::GetLastError() != ERROR_ALREADY_EXISTS) return false; NFind::CFileInfo fi; if (!fi.Find(us2fs(superPath))) return false; return fi.IsDir(); } } #endif } if (::GetLastError() != ERROR_ALREADY_EXISTS) return false; NFind::CFileInfo fi; if (!fi.Find(path)) return false; return fi.IsDir(); } bool CreateComplexDir(CFSTR _path) { #ifdef _WIN32 { DWORD attrib = NFind::GetFileAttrib(_path); if (attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) != 0) return true; } #ifndef UNDER_CE if (IsDriveRootPath_SuperAllowed(_path)) return false; unsigned prefixSize = GetRootPrefixSize(_path); #endif #endif FString path (_path); int pos = path.ReverseFind_PathSepar(); if (pos >= 0 && (unsigned)pos == path.Len() - 1) { if (path.Len() == 1) return true; path.DeleteBack(); } const FString path2 (path); pos = path.Len(); for (;;) { if (CreateDir2(path)) break; if (::GetLastError() == ERROR_ALREADY_EXISTS) return false; pos = path.ReverseFind_PathSepar(); if (pos < 0 || pos == 0) return false; #if defined(_WIN32) && !defined(UNDER_CE) if (pos == 1 && IS_PATH_SEPAR(path[0])) return false; if (prefixSize >= (unsigned)pos + 1) return false; #endif path.DeleteFrom(pos); } while (pos < (int)path2.Len()) { int pos2 = NName::FindSepar(path2.Ptr(pos + 1)); if (pos2 < 0) pos = path2.Len(); else pos += 1 + pos2; path.SetFrom(path2, pos); if (!CreateDir(path)) return false; } return true; } bool DeleteFileAlways(CFSTR path) { /* If alt stream, we also need to clear READ-ONLY attribute of main file before delete. SetFileAttrib("name:stream", ) changes attributes of main file. */ { DWORD attrib = NFind::GetFileAttrib(path); if (attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) == 0 && (attrib & FILE_ATTRIBUTE_READONLY) != 0) { if (!SetFileAttrib(path, attrib & ~FILE_ATTRIBUTE_READONLY)) return false; } } #ifndef _UNICODE if (!g_IsNT) { if (::DeleteFile(fs2fas(path))) return true; } else #endif { /* DeleteFile("name::$DATA") deletes all alt streams (same as delete DeleteFile("name")). Maybe it's better to open "name::$DATA" and clear data for unnamed stream? */ IF_USE_MAIN_PATH if (::DeleteFileW(fs2us(path))) return true; #ifdef WIN_LONG_PATH if (USE_SUPER_PATH) { UString superPath; if (GetSuperPath(path, superPath, USE_MAIN_PATH)) return BOOLToBool(::DeleteFileW(superPath)); } #endif } return false; } bool RemoveDirWithSubItems(const FString &path) { bool needRemoveSubItems = true; { NFind::CFileInfo fi; if (!fi.Find(path)) return false; if (!fi.IsDir()) { ::SetLastError(ERROR_DIRECTORY); return false; } if (fi.HasReparsePoint()) needRemoveSubItems = false; } if (needRemoveSubItems) { FString s (path); s.Add_PathSepar(); const unsigned prefixSize = s.Len(); NFind::CEnumerator enumerator; enumerator.SetDirPrefix(s); NFind::CFileInfo fi; while (enumerator.Next(fi)) { s.DeleteFrom(prefixSize); s += fi.Name; if (fi.IsDir()) { if (!RemoveDirWithSubItems(s)) return false; } else if (!DeleteFileAlways(s)) return false; } } if (!SetFileAttrib(path, 0)) return false; return RemoveDir(path); } #ifdef UNDER_CE bool MyGetFullPathName(CFSTR path, FString &resFullPath) { resFullPath = path; return true; } #else bool MyGetFullPathName(CFSTR path, FString &resFullPath) { return GetFullPath(path, resFullPath); } bool SetCurrentDir(CFSTR path) { // SetCurrentDirectory doesn't support \\?\ prefix #ifndef _UNICODE if (!g_IsNT) { return BOOLToBool(::SetCurrentDirectory(fs2fas(path))); } else #endif { return BOOLToBool(::SetCurrentDirectoryW(fs2us(path))); } } bool GetCurrentDir(FString &path) { path.Empty(); DWORD needLength; #ifndef _UNICODE if (!g_IsNT) { TCHAR s[MAX_PATH + 2]; s[0] = 0; needLength = ::GetCurrentDirectory(MAX_PATH + 1, s); path = fas2fs(s); } else #endif { WCHAR s[MAX_PATH + 2]; s[0] = 0; needLength = ::GetCurrentDirectoryW(MAX_PATH + 1, s); path = us2fs(s); } return (needLength > 0 && needLength <= MAX_PATH); } #endif bool GetFullPathAndSplit(CFSTR path, FString &resDirPrefix, FString &resFileName) { bool res = MyGetFullPathName(path, resDirPrefix); if (!res) resDirPrefix = path; int pos = resDirPrefix.ReverseFind_PathSepar(); resFileName = resDirPrefix.Ptr(pos + 1); resDirPrefix.DeleteFrom(pos + 1); return res; } bool GetOnlyDirPrefix(CFSTR path, FString &resDirPrefix) { FString resFileName; return GetFullPathAndSplit(path, resDirPrefix, resFileName); } bool MyGetTempPath(FString &path) { path.Empty(); DWORD needLength; #ifndef _UNICODE if (!g_IsNT) { TCHAR s[MAX_PATH + 2]; s[0] = 0; needLength = ::GetTempPath(MAX_PATH + 1, s); path = fas2fs(s); } else #endif { WCHAR s[MAX_PATH + 2]; s[0] = 0; needLength = ::GetTempPathW(MAX_PATH + 1, s);; path = us2fs(s); } return (needLength > 0 && needLength <= MAX_PATH); } static bool CreateTempFile(CFSTR prefix, bool addRandom, FString &path, NIO::COutFile *outFile) { UInt32 d = (GetTickCount() << 12) ^ (GetCurrentThreadId() << 14) ^ GetCurrentProcessId(); for (unsigned i = 0; i < 100; i++) { path = prefix; if (addRandom) { char s[16]; UInt32 val = d; unsigned k; for (k = 0; k < 8; k++) { unsigned t = val & 0xF; val >>= 4; s[k] = (char)((t < 10) ? ('0' + t) : ('A' + (t - 10))); } s[k] = '\0'; if (outFile) path += '.'; path += s; UInt32 step = GetTickCount() + 2; if (step == 0) step = 1; d += step; } addRandom = true; if (outFile) path += ".tmp"; if (NFind::DoesFileOrDirExist(path)) { SetLastError(ERROR_ALREADY_EXISTS); continue; } if (outFile) { if (outFile->Create(path, false)) return true; } else { if (CreateDir(path)) return true; } DWORD error = GetLastError(); if (error != ERROR_FILE_EXISTS && error != ERROR_ALREADY_EXISTS) break; } path.Empty(); return false; } bool CTempFile::Create(CFSTR prefix, NIO::COutFile *outFile) { if (!Remove()) return false; if (!CreateTempFile(prefix, false, _path, outFile)) return false; _mustBeDeleted = true; return true; } bool CTempFile::CreateRandomInTempFolder(CFSTR namePrefix, NIO::COutFile *outFile) { if (!Remove()) return false; FString tempPath; if (!MyGetTempPath(tempPath)) return false; if (!CreateTempFile(tempPath + namePrefix, true, _path, outFile)) return false; _mustBeDeleted = true; return true; } bool CTempFile::Remove() { if (!_mustBeDeleted) return true; _mustBeDeleted = !DeleteFileAlways(_path); return !_mustBeDeleted; } bool CTempFile::MoveTo(CFSTR name, bool deleteDestBefore) { // DWORD attrib = 0; if (deleteDestBefore) { if (NFind::DoesFileExist(name)) { // attrib = NFind::GetFileAttrib(name); if (!DeleteFileAlways(name)) return false; } } DisableDeleting(); return MyMoveFile(_path, name); /* if (attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_READONLY)) { DWORD attrib2 = NFind::GetFileAttrib(name); if (attrib2 != INVALID_FILE_ATTRIBUTES) SetFileAttrib(name, attrib2 | FILE_ATTRIBUTE_READONLY); } */ } bool CTempDir::Create(CFSTR prefix) { if (!Remove()) return false; FString tempPath; if (!MyGetTempPath(tempPath)) return false; if (!CreateTempFile(tempPath + prefix, true, _path, NULL)) return false; _mustBeDeleted = true; return true; } bool CTempDir::Remove() { if (!_mustBeDeleted) return true; _mustBeDeleted = !RemoveDirWithSubItems(_path); return !_mustBeDeleted; } }}}