diff options
Diffstat (limited to 'zen')
34 files changed, 2254 insertions, 990 deletions
diff --git a/zen/FindFilePlus/find_file_plus.cpp b/zen/FindFilePlus/find_file_plus.cpp index 46eb956c..ad385668 100644 --- a/zen/FindFilePlus/find_file_plus.cpp +++ b/zen/FindFilePlus/find_file_plus.cpp @@ -295,7 +295,7 @@ void FileSearcher::readDirImpl(FileInformation& output) //throw FileError QueryPolicy::extractFileId(dirInfo, output); output.creationTime = toFileTime(dirInfo.CreationTime); - output.lastWriteTime = toFileTime(dirInfo.LastWriteTime); + output.lastWriteTime = toFileTime(dirInfo.LastWriteTime); //the similar field "ChangeTime" refers to changes to metadata in addition to write accesses output.fileSize.QuadPart = dirInfo.EndOfFile.QuadPart; output.fileAttributes = dirInfo.FileAttributes; output.shortNameLength = dirInfo.FileNameLength / sizeof(TCHAR); //FileNameLength is in bytes! diff --git a/zen/FindFilePlus/find_file_plus.h b/zen/FindFilePlus/find_file_plus.h index 7306c32e..7d03d98b 100644 --- a/zen/FindFilePlus/find_file_plus.h +++ b/zen/FindFilePlus/find_file_plus.h @@ -45,7 +45,7 @@ typedef FileSearcher* FindHandle; DLL_FUNCTION_DECLARATION FindHandle openDir(const wchar_t* dirname); //returns nullptr on error, call ::GetLastError() -//note: do NOT place an asterisk at end, e.g. C:\SomeDir\*, as one would do for ::FindFirstFile() +//note: do NOT place an asterisk at end, e.g. C:\SomeDir\*, as you would do for ::FindFirstFile() DLL_FUNCTION_DECLARATION bool readDir(FindHandle hnd, FileInformation& output); //returns false on error or if there are no more files; ::GetLastError() returns ERROR_NO_MORE_FILES in this case @@ -53,7 +53,7 @@ bool readDir(FindHandle hnd, FileInformation& output); //returns false on error warning:; this may also return file system implementation dependent error codes like ERROR_NOT_SUPPORTED, ERROR_INVALID_LEVEL, ect. if "FileIdBothDirectoryInformation" is not supported! We need a fallback: - sometimes it's *not* sufficient to use fallback for NtQueryDirectoryFile() alone, we need to reset "hDir", since it may be f$ck$d $p by some poor file system layer implementation: + sometimes it's *not* sufficient to use fallback for NtQueryDirectoryFile() alone, we need to reset "hDir", since it may be fucked up by some poor file system layer implementation: - Samba before v3.0.22 (Mar 30, 2006) seems to have a bug which sucessfully returns 128 elements via NtQueryDirectoryFile() and FileIdBothDirectoryInformation, then fails with STATUS_INVALID_LEVEL. Fallback to FileBothDirectoryInformation will return STATUS_NO_MORE_FILES, even if there *are* more files - NtQueryDirectoryFile() may *not* respect "restartScan" for some weird Win2000 file system drivers, so we cannot rely on this as a replacement for a "hDir" reset @@ -69,17 +69,17 @@ void closeDir(FindHandle hnd); /*---------- |typedefs| ----------*/ -typedef FindHandle (*OpenDirFunc )(const wchar_t* dirname); -typedef bool (*ReadDirFunc )(FindHandle hnd, FileInformation& dirInfo); -typedef void (*CloseDirFunc)(FindHandle hnd); +typedef FindHandle (*FunType_openDir )(const wchar_t* dirname); +typedef bool (*FunType_readDir )(FindHandle hnd, FileInformation& dirInfo); +typedef void (*FunType_closeDir)(FindHandle hnd); /*-------------- |symbol names| --------------*/ //const pointers ensure internal linkage -const char openDirFuncName [] = "openDir"; -const char readDirFuncName [] = "readDir"; -const char closeDirFuncName[] = "closeDir"; +const char funName_openDir [] = "openDir"; +const char funName_readDir [] = "readDir"; +const char funName_closeDir[] = "closeDir"; /*--------------- |library names| diff --git a/zen/IFileOperation/FileOperation_Vista.vcxproj b/zen/IFileOperation/FileOperation_Vista.vcxproj new file mode 100644 index 00000000..a387dcb5 --- /dev/null +++ b/zen/IFileOperation/FileOperation_Vista.vcxproj @@ -0,0 +1,244 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|x64"> + <Configuration>Debug</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|x64"> + <Configuration>Release</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <ProjectName>Vista IFileOperation</ProjectName> + <ProjectGuid>{70394AEF-5897-4911-AFA1-82EAF0581EFA}</ProjectGuid> + <RootNamespace>ShadowDll</RootNamespace> + <Keyword>Win32Proj</Keyword> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <CharacterSet>Unicode</CharacterSet> + <WholeProgramOptimization>true</WholeProgramOptimization> + <PlatformToolset>Windows7.1SDK</PlatformToolset> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <CharacterSet>Unicode</CharacterSet> + <PlatformToolset>Windows7.1SDK</PlatformToolset> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <CharacterSet>Unicode</CharacterSet> + <WholeProgramOptimization>true</WholeProgramOptimization> + <PlatformToolset>Windows7.1SDK</PlatformToolset> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <CharacterSet>Unicode</CharacterSet> + <PlatformToolset>Windows7.1SDK</PlatformToolset> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup> + <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion> + <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">OBJ\</OutDir> + <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">OBJ\$(ProjectName)_$(Configuration)_$(Platform)\</IntDir> + <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</LinkIncremental> + <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">OBJ\</OutDir> + <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">OBJ\$(ProjectName)_$(Configuration)_$(Platform)\</IntDir> + <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</LinkIncremental> + <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.\</OutDir> + <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">OBJ\$(ProjectName)_$(Configuration)_$(Platform)\</IntDir> + <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental> + <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\</OutDir> + <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">OBJ\$(ProjectName)_$(Configuration)_$(Platform)\</IntDir> + <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</LinkIncremental> + <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">FileOperation_$(Platform)</TargetName> + <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">FileOperation_$(Platform)</TargetName> + <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">FileOperation_$(Platform)</TargetName> + <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">FileOperation_$(Platform)</TargetName> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <BuildLog> + <Path>$(IntDir)Build.html</Path> + </BuildLog> + <ClCompile> + <Optimization>Disabled</Optimization> + <PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;FILE_OP_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>true</MinimalRebuild> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <PrecompiledHeader> + </PrecompiledHeader> + <WarningLevel>Level4</WarningLevel> + <SuppressStartupBanner>true</SuppressStartupBanner> + <DebugInformationFormat>EditAndContinue</DebugInformationFormat> + <DisableSpecificWarnings>4100;4996</DisableSpecificWarnings> + <AdditionalIncludeDirectories>../..;C:\Program Files\C++\boost</AdditionalIncludeDirectories> + </ClCompile> + <Link> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <SuppressStartupBanner>true</SuppressStartupBanner> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <SubSystem>Windows</SubSystem> + <ProfileGuidedDatabase> + </ProfileGuidedDatabase> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + <AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage\lib</AdditionalLibraryDirectories> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <BuildLog> + <Path>$(IntDir)Build.html</Path> + </BuildLog> + <Midl> + <TargetEnvironment>X64</TargetEnvironment> + </Midl> + <ClCompile> + <Optimization>Disabled</Optimization> + <PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;FILE_OP_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>true</MinimalRebuild> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <PrecompiledHeader> + </PrecompiledHeader> + <WarningLevel>Level4</WarningLevel> + <SuppressStartupBanner>true</SuppressStartupBanner> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <DisableSpecificWarnings>4100;4996</DisableSpecificWarnings> + <AdditionalIncludeDirectories>../..;C:\Program Files\C++\boost</AdditionalIncludeDirectories> + </ClCompile> + <Link> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <SuppressStartupBanner>true</SuppressStartupBanner> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <SubSystem>Windows</SubSystem> + <ProfileGuidedDatabase> + </ProfileGuidedDatabase> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <TargetMachine>MachineX64</TargetMachine> + <AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage64\lib</AdditionalLibraryDirectories> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <BuildLog> + <Path>$(IntDir)Build.html</Path> + </BuildLog> + <ClCompile> + <Optimization>MaxSpeed</Optimization> + <IntrinsicFunctions>true</IntrinsicFunctions> + <PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;FILE_OP_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <RuntimeLibrary>MultiThreaded</RuntimeLibrary> + <FunctionLevelLinking>true</FunctionLevelLinking> + <PrecompiledHeader> + </PrecompiledHeader> + <WarningLevel>Level4</WarningLevel> + <SuppressStartupBanner>true</SuppressStartupBanner> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed> + <DisableSpecificWarnings>4100;4996</DisableSpecificWarnings> + <AdditionalIncludeDirectories>../..;C:\Program Files\C++\boost</AdditionalIncludeDirectories> + </ClCompile> + <Link> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <SuppressStartupBanner>true</SuppressStartupBanner> + <GenerateDebugInformation>false</GenerateDebugInformation> + <SubSystem>Windows</SubSystem> + <OptimizeReferences>true</OptimizeReferences> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration> + <ProfileGuidedDatabase> + </ProfileGuidedDatabase> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + <AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage\lib</AdditionalLibraryDirectories> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <BuildLog> + <Path>$(IntDir)Build.html</Path> + </BuildLog> + <Midl> + <TargetEnvironment>X64</TargetEnvironment> + </Midl> + <ClCompile> + <Optimization>MaxSpeed</Optimization> + <IntrinsicFunctions>true</IntrinsicFunctions> + <PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;FILE_OP_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <RuntimeLibrary>MultiThreaded</RuntimeLibrary> + <FunctionLevelLinking>true</FunctionLevelLinking> + <PrecompiledHeader> + </PrecompiledHeader> + <WarningLevel>Level4</WarningLevel> + <SuppressStartupBanner>true</SuppressStartupBanner> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed> + <DisableSpecificWarnings>4100;4996</DisableSpecificWarnings> + <AdditionalIncludeDirectories>../..;C:\Program Files\C++\boost</AdditionalIncludeDirectories> + </ClCompile> + <Link> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <SuppressStartupBanner>true</SuppressStartupBanner> + <GenerateDebugInformation>false</GenerateDebugInformation> + <SubSystem>Windows</SubSystem> + <OptimizeReferences>true</OptimizeReferences> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration> + <ProfileGuidedDatabase> + </ProfileGuidedDatabase> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <TargetMachine>MachineX64</TargetMachine> + <AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage64\lib</AdditionalLibraryDirectories> + </Link> + </ItemDefinitionGroup> + <ItemGroup> + <ClCompile Include="dll_main.cpp"> + <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + </PrecompiledHeader> + <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</CompileAsManaged> + <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + </PrecompiledHeader> + <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</CompileAsManaged> + <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + </PrecompiledHeader> + <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</CompileAsManaged> + <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + </PrecompiledHeader> + <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</CompileAsManaged> + </ClCompile> + <ClCompile Include="file_op.cpp" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="file_op.h" /> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project>
\ No newline at end of file diff --git a/zen/IFileOperation/dll_main.cpp b/zen/IFileOperation/dll_main.cpp new file mode 100644 index 00000000..46c65311 --- /dev/null +++ b/zen/IFileOperation/dll_main.cpp @@ -0,0 +1,25 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +//optional: add init/teardown logic here +BOOL APIENTRY DllMain(HINSTANCE hinstDLL, + DWORD fdwReason, + LPVOID lpvReserved) +{ + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + case DLL_PROCESS_DETACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + } + return TRUE; +} diff --git a/zen/IFileOperation/file_op.cpp b/zen/IFileOperation/file_op.cpp new file mode 100644 index 00000000..7c75a8e8 --- /dev/null +++ b/zen/IFileOperation/file_op.cpp @@ -0,0 +1,349 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#include "file_op.h" +#include <algorithm> +#include <string> +#include <vector> + +#define WIN32_LEAN_AND_MEAN +#include <zen/com_ptr.h> +#include <zen/com_error.h> +#include <zen/scope_guard.h> + +#include <boost/thread/tss.hpp> + +#include <RestartManager.h> +#pragma comment(lib, "Rstrtmgr.lib") + +#define STRICT_TYPED_ITEMIDS //better type safety for IDLists +#include <Shlobj.h> +#include <shobjidl.h> +#include <shellapi.h> //shell constants such as FO_* values + +using namespace zen; + + +namespace +{ +void moveToRecycleBin(const wchar_t* fileNames[], //throw ComError + size_t fileNo) //size of fileNames array +{ + ComPtr<IFileOperation> fileOp; + ZEN_CHECK_COM(::CoCreateInstance(CLSID_FileOperation, //throw ComError + nullptr, + CLSCTX_ALL, + IID_PPV_ARGS(fileOp.init()))); + + // Set the operation flags. Turn off all UI + // from being shown to the user during the + // operation. This includes error, confirmation + // and progress dialogs. + ZEN_CHECK_COM(fileOp->SetOperationFlags(FOF_ALLOWUNDO | //throw ComError + FOF_NOCONFIRMATION | + FOF_SILENT | + FOF_NOERRORUI | + FOFX_EARLYFAILURE | + FOF_NO_CONNECTED_ELEMENTS)); + + int operationCount = 0; + + for (size_t i = 0; i < fileNo; ++i) + { + //create file/folder item object + ComPtr<IShellItem> psiFile; + HRESULT hr = ::SHCreateItemFromParsingName(fileNames[i], + nullptr, + IID_PPV_ARGS(psiFile.init())); + if (FAILED(hr)) + { + if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) || //file not existing anymore + hr == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)) + continue; + throw ComError(std::wstring(L"Error calling \"SHCreateItemFromParsingName\" for file:\n") + L"\"" + fileNames[i] + L"\".", hr); + } + + ZEN_CHECK_COM(fileOp->DeleteItem(psiFile.get(), nullptr)); + + ++operationCount; + } + + if (operationCount == 0) //calling PerformOperations() without anything to do would result in E_UNEXPECTED + return; + + //perform actual operations + ZEN_CHECK_COM(fileOp->PerformOperations()); + + //check if errors occured: if FOFX_EARLYFAILURE is not used, PerformOperations() can return with success despite errors! + BOOL pfAnyOperationsAborted = FALSE; + ZEN_CHECK_COM(fileOp->GetAnyOperationsAborted(&pfAnyOperationsAborted)); + + if (pfAnyOperationsAborted == TRUE) + throw ComError(L"Operation did not complete successfully."); +} + + +void copyFile(const wchar_t* sourceFile, //throw ComError + const wchar_t* targetFile) +{ + ComPtr<IFileOperation> fileOp; + ZEN_CHECK_COM(::CoCreateInstance(CLSID_FileOperation, //throw ComError + nullptr, + CLSCTX_ALL, + IID_PPV_ARGS(fileOp.init()))); + + // Set the operation flags. Turn off all UI + // from being shown to the user during the + // operation. This includes error, confirmation + // and progress dialogs. + ZEN_CHECK_COM(fileOp->SetOperationFlags(FOF_NOCONFIRMATION | //throw ComError + FOF_SILENT | + FOFX_EARLYFAILURE | + FOF_NOERRORUI)); + //create source object + ComPtr<IShellItem> psiSourceFile; + { + HRESULT hr = ::SHCreateItemFromParsingName(sourceFile, + nullptr, + IID_PPV_ARGS(psiSourceFile.init())); + if (FAILED(hr)) + throw ComError(std::wstring(L"Error calling \"SHCreateItemFromParsingName\" for file:\n") + L"\"" + sourceFile + L"\".", hr); + } + + const size_t pos = std::wstring(targetFile).find_last_of(L'\\'); + if (pos == std::wstring::npos) + throw ComError(L"Target filename does not contain a path separator."); + + const std::wstring targetFolder(targetFile, pos); + const std::wstring targetFileNameShort = targetFile + pos + 1; + + //create target folder object + ComPtr<IShellItem> psiTargetFolder; + { + HRESULT hr = ::SHCreateItemFromParsingName(targetFolder.c_str(), + nullptr, + IID_PPV_ARGS(psiTargetFolder.init())); + if (FAILED(hr)) + throw ComError(std::wstring(L"Error calling \"SHCreateItemFromParsingName\" for folder:\n") + L"\"" + targetFolder + L"\".", hr); + } + + //schedule file copy operation + ZEN_CHECK_COM(fileOp->CopyItem(psiSourceFile.get(), psiTargetFolder.get(), targetFileNameShort.c_str(), nullptr)); + + //perform actual operations + ZEN_CHECK_COM(fileOp->PerformOperations()); + + //check if errors occured: if FOFX_EARLYFAILURE is not used, PerformOperations() can return with success despite errors! + BOOL pfAnyOperationsAborted = FALSE; + ZEN_CHECK_COM(fileOp->GetAnyOperationsAborted(&pfAnyOperationsAborted)); + + if (pfAnyOperationsAborted == TRUE) + throw ComError(L"Operation did not complete successfully."); +} + + +void getFolderClsid(const wchar_t* dirname, CLSID& pathCLSID) //throw ComError +{ + ComPtr<IShellFolder> desktopFolder; + ZEN_CHECK_COM(::SHGetDesktopFolder(desktopFolder.init())); //throw ComError + + PIDLIST_RELATIVE pidlFolder = nullptr; + ZEN_CHECK_COM(desktopFolder->ParseDisplayName(nullptr, // [in] HWND hwnd, + nullptr, // [in] IBindCtx *pbc, + const_cast<LPWSTR>(dirname), // [in] LPWSTR pszDisplayName, + nullptr, // [out] ULONG *pchEaten, + &pidlFolder, // [out] PIDLIST_RELATIVE* ppidl, + nullptr)); // [in, out] ULONG *pdwAttributes + ZEN_ON_SCOPE_EXIT(::ILFree(pidlFolder)); //older version: ::CoTaskMemFree + + ComPtr<IPersist> persistFolder; + ZEN_CHECK_COM(desktopFolder->BindToObject(pidlFolder, // [in] PCUIDLIST_RELATIVE pidl, + nullptr, // [in] IBindCtx *pbc, + IID_PPV_ARGS(persistFolder.init()))); //throw ComError + + ZEN_CHECK_COM(persistFolder->GetClassID(&pathCLSID)); //throw ComError +} + + +struct Win32Error +{ + Win32Error(DWORD errorCode) : errorCode_(errorCode) {} + DWORD errorCode_; +}; + +std::vector<std::wstring> getLockingProcesses(const wchar_t* filename) //throw Win32Error +{ + wchar_t sessionKey[CCH_RM_SESSION_KEY + 1] = {}; //fixes two bugs: http://blogs.msdn.com/b/oldnewthing/archive/2012/02/17/10268840.aspx + DWORD sessionHandle = 0; + DWORD rv1 = ::RmStartSession(&sessionHandle, //__out DWORD *pSessionHandle, + 0, //__reserved DWORD dwSessionFlags, + sessionKey); //__out WCHAR strSessionKey[ ] + if (rv1 != ERROR_SUCCESS) + throw Win32Error(rv1); + ZEN_ON_SCOPE_EXIT(::RmEndSession(sessionHandle)); + + DWORD rv2 = ::RmRegisterResources(sessionHandle, //__in DWORD dwSessionHandle, + 1, //__in UINT nFiles, + &filename, //__in_opt LPCWSTR rgsFilenames[ ], + 0, //__in UINT nApplications, + nullptr, //__in_opt RM_UNIQUE_PROCESS rgApplications[ ], + 0, //__in UINT nServices, + nullptr); //__in_opt LPCWSTR rgsServiceNames[ ] + if (rv2 != ERROR_SUCCESS) + throw Win32Error(rv2); + + UINT procInfoSize = 0; + UINT procInfoSizeNeeded = 0; + DWORD rebootReasons = 0; + ::RmGetList(sessionHandle, &procInfoSizeNeeded, &procInfoSize, nullptr, &rebootReasons); //get procInfoSizeNeeded + //fails with "access denied" for C:\pagefile.sys! + + if (procInfoSizeNeeded == 0) + return std::vector<std::wstring>(); + + procInfoSize = procInfoSizeNeeded; + std::vector<RM_PROCESS_INFO> procInfo(procInfoSize); + + DWORD rv3 = ::RmGetList(sessionHandle, //__in DWORD dwSessionHandle, + &procInfoSizeNeeded, //__out UINT *pnProcInfoNeeded, + &procInfoSize, //__inout UINT *pnProcInfo, + &procInfo[0], //__inout_opt RM_PROCESS_INFO rgAffectedApps[ ], + &rebootReasons); //__out LPDWORD lpdwRebootReasons + if (rv3 != ERROR_SUCCESS) + throw Win32Error(rv3); + procInfo.resize(procInfoSize); + + std::vector<std::wstring> output; + for (auto iter = procInfo.begin(); iter != procInfo.end(); ++iter) + { + std::wstring processName = iter->strAppName; + + //try to get process path + HANDLE hProcess = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, //__in DWORD dwDesiredAccess, + false, //__in BOOL bInheritHandle, + iter->Process.dwProcessId); //__in DWORD dwProcessId + if (hProcess) + { + ZEN_ON_SCOPE_EXIT(::CloseHandle(hProcess)); + + FILETIME creationTime = {}; + FILETIME exitTime = {}; + FILETIME kernelTime = {}; + FILETIME userTime = {}; + if (::GetProcessTimes(hProcess, //__in HANDLE hProcess, + &creationTime, //__out LPFILETIME lpCreationTime, + &exitTime, //__out LPFILETIME lpExitTime, + &kernelTime, //__out LPFILETIME lpKernelTime, + &userTime)) //__out LPFILETIME lpUserTime + if (::CompareFileTime(&iter->Process.ProcessStartTime, &creationTime) == 0) + { + DWORD bufferSize = MAX_PATH; + std::vector<wchar_t> buffer(bufferSize); + if (::QueryFullProcessImageName(hProcess, //__in HANDLE hProcess, + 0, //__in DWORD dwFlags, + &buffer[0], //__out LPTSTR lpExeName, + &bufferSize)) //__inout PDWORD lpdwSize + if (bufferSize < buffer.size()) + processName += std::wstring(L" - ") + L"\"" + &buffer[0] + L"\""; + } + } + output.push_back(processName); + } + return output; +} + + +boost::thread_specific_ptr<std::wstring> lastErrorMessage; //use "thread_local" in C++11 +} + + +bool fileop::moveToRecycleBin(const wchar_t* fileNames[], size_t fileNo) //size of fileNames array +{ + try + { + ::moveToRecycleBin(fileNames, fileNo); //throw ComError + return true; + } + catch (const ComError& e) + { + lastErrorMessage.reset(new std::wstring(e.toString())); + return false; + } +} + + +bool fileop::copyFile(const wchar_t* sourceFile, + const wchar_t* targetFile) +{ + try + { + ::copyFile(sourceFile, targetFile); //throw ComError + return true; + } + catch (const ComError& e) + { + lastErrorMessage.reset(new std::wstring(e.toString())); + return false; + } +} + + +bool fileop::checkRecycler(const wchar_t* dirname, bool& isRecycler) +{ + try + { + CLSID clsid = {}; + getFolderClsid(dirname, clsid); //throw ComError + isRecycler = ::IsEqualCLSID(clsid, CLSID_RecycleBin) == TRUE; //silence perf warning + return true; + } + catch (const ComError& e) + { + lastErrorMessage.reset(new std::wstring(e.toString())); + return false; + } +} + + +const wchar_t* fileop::getLastError() +{ + return !lastErrorMessage.get() ? L"" : lastErrorMessage->c_str(); +} + + +bool fileop::getLockingProcesses(const wchar_t* filename, const wchar_t*& procList) +{ + try + { + std::vector<std::wstring> result = ::getLockingProcesses(filename); //throw Win32Error + + std::wstring buffer; + for (auto iter = result.begin(); iter != result.end(); ++iter) + { + buffer += *iter; + buffer += L'\n'; + } + if (!buffer.empty()) + buffer.resize(buffer.size() - 1); //remove last line break + + auto tmp = new wchar_t [buffer.size() + 1]; //bad_alloc ? + ::wmemcpy(tmp, buffer.c_str(), buffer.size() + 1); //include 0-termination + procList = tmp; //ownership passed + + return true; + } + catch (const Win32Error& e) + { + lastErrorMessage.reset(new std::wstring(formatWin32Msg(e.errorCode_))); + return false; + } +} + + +void fileop::freeString(const wchar_t* str) +{ + delete [] str; +} diff --git a/zen/IFileOperation/file_op.h b/zen/IFileOperation/file_op.h new file mode 100644 index 00000000..fb157301 --- /dev/null +++ b/zen/IFileOperation/file_op.h @@ -0,0 +1,76 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#ifndef RECYCLER_DLL_H +#define RECYCLER_DLL_H + +#ifdef FILE_OP_DLL_EXPORTS +#define FILE_OP_DLL_API extern "C" __declspec(dllexport) +#else +#define FILE_OP_DLL_API extern "C" __declspec(dllimport) +#endif + +#include <zen/build_info.h> + + +namespace fileop +{ +/*-------------- + |declarations| + --------------*/ + +//COM needs to be initialized before calling any of these functions! CoInitializeEx/CoUninitialize +//minimum OS: Windows Vista or later + +FILE_OP_DLL_API +bool moveToRecycleBin(const wchar_t* fileNames[], + size_t fileNo); //size of fileNames array + +FILE_OP_DLL_API +bool copyFile(const wchar_t* sourceFile, + const wchar_t* targetFile); + +FILE_OP_DLL_API +bool checkRecycler(const wchar_t* dirname, bool& isRecycler); //returns false on error + +FILE_OP_DLL_API +bool getLockingProcesses(const wchar_t* filename, const wchar_t*& procList); //get list of processes as single string, call freeString(procList) after use! + +FILE_OP_DLL_API +void freeString(const wchar_t* str); + +//get last error message if any of the functions above fail +FILE_OP_DLL_API +const wchar_t* getLastError(); //no nullptr check required! + +/*---------- + |typedefs| + ----------*/ +typedef bool (*FunType_moveToRecycleBin)(const wchar_t* fileNames[], size_t fileNo); +typedef bool (*FunType_copyFile)(const wchar_t* sourceFile, const wchar_t* targetFile); +typedef bool (*FunType_checkRecycler)(const wchar_t* dirname, bool& isRecycler); +typedef bool (*FunType_getLockingProcesses)(const wchar_t* filename, const wchar_t*& procList); +typedef void (*FunType_freeString)(const wchar_t* str); +typedef const wchar_t* (*FunType_getLastError)(); + +/*-------------- + |symbol names| + --------------*/ +//(use const pointers to ensure internal linkage) +const char funName_moveToRecycleBin [] = "moveToRecycleBin"; +const char funName_copyFile [] = "copyFile"; +const char funName_checkRecycler [] = "checkRecycler"; +const char funName_getLockingProcesses[] = "getLockingProcesses"; +const char funName_freeString [] = "freeString"; +const char funName_getLastError [] = "getLastError"; + +/*--------------- + |library names| + ---------------*/ +inline const wchar_t* getDllName() { return zen::is64BitBuild ? L"FileOperation_x64.dll" : L"FileOperation_Win32.dll"; } +} + +#endif //RECYCLER_DLL_H diff --git a/zen/com_error.h b/zen/com_error.h index c67c4193..d891fa13 100644 --- a/zen/com_error.h +++ b/zen/com_error.h @@ -212,17 +212,16 @@ std::wstring generateErrorMsg(const std::wstring& input, HRESULT hr) { std::wstring output(input); output += L"\n"; - output += L"HRESULT: " + numberToHexString(hr) + L",\n"; //don't use _com_error(hr).ErrorMessage(): internally this is nothing more than a call to ::FormatMessage() std::wstring win32Msg = formatWin32Msg(hr); if (!win32Msg.empty()) //empty string on error - output += win32Msg; + output += win32Msg + L"\n" + L"HRESULT: " + numberToHexString(hr); else - { - output += L"Facility: " + formatFacility(hr) + L",\n"; - output += L"Win32 Error: " + formatWin32Msg(HRESULT_CODE(hr)); //interpret hr as a Win32 code; this is often useful... - } + output += L"HRESULT: " + numberToHexString(hr) + L", " + L"Facility: " + formatFacility(hr); + //don't bluntly interpret as Win32 error code HRESULT_CODE(hr), too often misleading! + //http://blogs.msdn.com/b/oldnewthing/archive/2006/11/03/942851.aspx + return output; } diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index ed07a1e4..19c56d5a 100644 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -50,7 +50,7 @@ public: const Zstring fullname = dirname + Zstring(notifyInfo.FileName, notifyInfo.FileNameLength / sizeof(WCHAR)); //skip modifications sent by changed directories: reason for change, child element creation/deletion, will notify separately! - [&]() + [&] { if (notifyInfo.Action == FILE_ACTION_RENAMED_OLD_NAME) //reporting FILE_ACTION_RENAMED_NEW_NAME should suffice return; @@ -125,12 +125,9 @@ public: ReadChangesAsync(const Zstring& directory, //make sure to not leak in thread-unsafe types! const std::shared_ptr<SharedData>& shared) : shared_(shared), - dirname(directory), + dirnamePf(appendSeparator(directory)), hDir(INVALID_HANDLE_VALUE) { - if (!endsWith(dirname, FILE_NAME_SEPARATOR)) - dirname += FILE_NAME_SEPARATOR; - //these two privileges are required by ::CreateFile FILE_FLAG_BACKUP_SEMANTICS according to //http://msdn.microsoft.com/en-us/library/aa363858(v=vs.85).aspx try @@ -140,7 +137,7 @@ public: } catch (const FileError&) {} - hDir = ::CreateFile(applyLongPathPrefix(dirname.c_str()).c_str(), + hDir = ::CreateFile(applyLongPathPrefix(dirnamePf.c_str()).c_str(), FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, @@ -149,8 +146,9 @@ public: nullptr); if (hDir == INVALID_HANDLE_VALUE) { - const std::wstring errorMsg = _("Could not initialize directory monitoring:") + L"\n\"" + dirname + L"\"" L"\n\n" + zen::getLastErrorFormatted(); - if (errorCodeForNotExisting(::GetLastError())) + const DWORD lastError = ::GetLastError(); + const std::wstring errorMsg = replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + getLastErrorFormatted(lastError); + if (errorCodeForNotExisting(lastError)) throw ErrorNotExisting(errorMsg); throw FileError(errorMsg); } @@ -181,14 +179,15 @@ public: false, //__in BOOL bInitialState, nullptr); //__in_opt LPCTSTR lpName if (overlapped.hEvent == nullptr) - return shared_->reportError(_("Error when monitoring directories.") + L" (CreateEvent)" L"\n\n" + getLastErrorFormatted(), ::GetLastError()); + return shared_->reportError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(dirnamePf)) + L" (CreateEvent)" L"\n\n" + getLastErrorFormatted(), ::GetLastError()); + ZEN_ON_SCOPE_EXIT(::CloseHandle(overlapped.hEvent)); //asynchronous variant: runs on this thread's APC queue! - if (!::ReadDirectoryChangesW(hDir, // __in HANDLE hDirectory, - &buffer[0], // __out LPVOID lpBuffer, - static_cast<DWORD>(buffer.size()), // __in DWORD nBufferLength, - true, // __in BOOL bWatchSubtree, + if (!::ReadDirectoryChangesW(hDir, // __in HANDLE hDirectory, + &buffer[0], // __out LPVOID lpBuffer, + static_cast<DWORD>(buffer.size()), // __in DWORD nBufferLength, + true, // __in BOOL bWatchSubtree, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_SIZE | @@ -196,7 +195,7 @@ public: nullptr, // __out_opt LPDWORD lpBytesReturned, &overlapped, // __inout_opt LPOVERLAPPED lpOverlapped, nullptr)) // __in_opt LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine - return shared_->reportError(_("Error when monitoring directories.") + L" (ReadDirectoryChangesW)" L"\n\n" + getLastErrorFormatted(), ::GetLastError()); + return shared_->reportError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(dirnamePf)) + L" (ReadDirectoryChangesW)" L"\n\n" + getLastErrorFormatted(), ::GetLastError()); //async I/O is a resource that needs to be guarded since it will write to local variable "buffer"! zen::ScopeGuard guardAio = zen::makeGuard([&] @@ -217,7 +216,7 @@ public: false)) //__in BOOL bWait { if (::GetLastError() != ERROR_IO_INCOMPLETE) - return shared_->reportError(_("Error when monitoring directories.") + L" (GetOverlappedResult)" L"\n\n" + getLastErrorFormatted(), ::GetLastError()); + return shared_->reportError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(dirnamePf)) + L" (GetOverlappedResult)" L"\n\n" + getLastErrorFormatted(), ::GetLastError()); //execute asynchronous procedure calls (APC) queued on this thread ::SleepEx(50, // __in DWORD dwMilliseconds, @@ -227,7 +226,7 @@ public: } guardAio.dismiss(); - shared_->addChanges(&buffer[0], bytesWritten, dirname); //throw () + shared_->addChanges(&buffer[0], bytesWritten, dirnamePf); //throw () } } catch (boost::thread_interrupted&) @@ -236,21 +235,24 @@ public: } } - ReadChangesAsync(ReadChangesAsync && other) : + ReadChangesAsync(ReadChangesAsync&& other) : hDir(INVALID_HANDLE_VALUE) { - shared_ = std::move(other.shared_); - dirname = std::move(other.dirname); + shared_ = std::move(other.shared_); + dirnamePf = std::move(other.dirnamePf); std::swap(hDir, other.hDir); } HANDLE getDirHandle() const { return hDir; } //for reading/monitoring purposes only, don't abuse (e.g. close handle)! private: + ReadChangesAsync(const ReadChangesAsync&); + ReadChangesAsync& operator=(const ReadChangesAsync&); + //shared between main and worker: std::shared_ptr<SharedData> shared_; //worker thread only: - Zstring dirname; //thread safe! + Zstring dirnamePf; //thread safe! HANDLE hDir; }; @@ -347,8 +349,9 @@ std::vector<Zstring> DirWatcher::getChanges(const std::function<void()>& process #elif defined FFS_LINUX struct DirWatcher::Pimpl { + Zstring dirname; int notifDescr; - std::map<int, Zstring> watchDescrs; //watch descriptor and corresponding directory name (postfixed with separator!) + std::map<int, Zstring> watchDescrs; //watch descriptor and corresponding (sub-)directory name (postfixed with separator!) }; @@ -394,11 +397,12 @@ DirWatcher::DirWatcher(const Zstring& directory) : //throw FileError zen::traverseFolder(dirname, false, *traverser); //don't traverse into symlinks (analog to windows build) //init + pimpl_->dirname = directory; pimpl_->notifDescr = ::inotify_init(); if (pimpl_->notifDescr == -1) - throw FileError(_("Could not initialize directory monitoring:") + L"\n\"" + dirname + L"\"" + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(dirname)) + L"\n\n" + getLastErrorFormatted()); - zen::ScopeGuard guardDescr = zen::makeGuard([&]() { ::close(pimpl_->notifDescr); }); + zen::ScopeGuard guardDescr = zen::makeGuard([&] { ::close(pimpl_->notifDescr); }); //set non-blocking mode bool initSuccess = false; @@ -407,7 +411,7 @@ DirWatcher::DirWatcher(const Zstring& directory) : //throw FileError initSuccess = ::fcntl(pimpl_->notifDescr, F_SETFL, flags | O_NONBLOCK) != -1; if (!initSuccess) - throw FileError(_("Could not initialize directory monitoring:") + L"\n\"" + dirname + L"\"" + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(dirname)) + L"\n\n" + getLastErrorFormatted()); //add watches std::for_each(fullDirList.begin(), fullDirList.end(), @@ -425,15 +429,13 @@ DirWatcher::DirWatcher(const Zstring& directory) : //throw FileError IN_MOVE_SELF); if (wd == -1) { - std::wstring errorMsg = _("Could not initialize directory monitoring:") + L"\n\"" + subdir + L"\"" + L"\n\n" + getLastErrorFormatted(); - if (errno == ENOENT) + const std::wstring errorMsg = replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(subdir)) + L"\n\n" + getLastErrorFormatted(); + if (errorCodeForNotExisting(errno)) throw ErrorNotExisting(errorMsg); throw FileError(errorMsg); } - if (!endsWith(subdir, FILE_NAME_SEPARATOR)) - subdir += FILE_NAME_SEPARATOR; - pimpl_->watchDescrs.insert(std::make_pair(wd, subdir)); + pimpl_->watchDescrs.insert(std::make_pair(wd, appendSeparator(subdir))); }); guardDescr.dismiss(); @@ -448,9 +450,8 @@ DirWatcher::~DirWatcher() std::vector<Zstring> DirWatcher::getChanges(const std::function<void()>&) //throw FileError { - std::vector<char> buffer(1024 * (sizeof(struct inotify_event) + 16)); - //non-blocking call, see O_NONBLOCK + std::vector<char> buffer(1024 * (sizeof(struct inotify_event) + 16)); ssize_t bytesRead = ::read(pimpl_->notifDescr, &buffer[0], buffer.size()); if (bytesRead == -1) @@ -459,7 +460,7 @@ std::vector<Zstring> DirWatcher::getChanges(const std::function<void()>&) //thro errno == EAGAIN) //Non-blocking I/O has been selected using O_NONBLOCK and no data was immediately available for reading return std::vector<Zstring>(); - throw FileError(_("Error when monitoring directories.") + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(pimpl_->dirname)) + L"\n\n" + getLastErrorFormatted()); } std::set<Zstring> tmp; //get rid of duplicate entries (actually occur!) diff --git a/zen/dst_hack.cpp b/zen/dst_hack.cpp index 3ecfd8e9..9457b966 100644 --- a/zen/dst_hack.cpp +++ b/zen/dst_hack.cpp @@ -21,13 +21,9 @@ Zstring getVolumeName(const Zstring& filename) // fsName, //__out LPTSTR lpszVolumePathName, // BUFFER_SIZE)) //__in DWORD cchBufferLength // ... - // Zstring volumePath = fsName; - // if (!volumePath.EndsWith(FILE_NAME_SEPARATOR)) //a trailing backslash is required - // volumePath += FILE_NAME_SEPARATOR; + // Zstring volumePath = appendSeparator(fsName); - Zstring nameFmt = removeLongPathPrefix(filename); //throw() - if (!endsWith(nameFmt, FILE_NAME_SEPARATOR)) - nameFmt += FILE_NAME_SEPARATOR; //GetVolumeInformation expects trailing backslash + const Zstring nameFmt = appendSeparator(removeLongPathPrefix(filename)); //throw() if (startsWith(nameFmt, Zstr("\\\\"))) //UNC path: "\\ComputerName\SharedFolder\" { @@ -82,8 +78,10 @@ bool dst::isFatDrive(const Zstring& fileName) //throw() } +/* bool dst::isFatDrive(HANDLE hFile) //throw() { +Requires Windows Vista! //dynamically load windows API function typedef BOOL (WINAPI* GetVolumeInformationByHandleWFunc)(HANDLE hFile, LPWSTR lpVolumeNameBuffer, @@ -120,6 +118,7 @@ bool dst::isFatDrive(HANDLE hFile) //throw() return fsName == Zstring(L"FAT") || fsName == Zstring(L"FAT32"); } +*/ namespace @@ -159,7 +158,7 @@ Int64 toInt64(const FILETIME& fileTime) inline -FILETIME utcToLocal(const FILETIME& utcTime) //throw (std::runtime_error) +FILETIME utcToLocal(const FILETIME& utcTime) //throw std::runtime_error { //treat binary local time representation (which is invariant under DST time zone shift) as logical UTC: FILETIME localTime = {}; @@ -177,7 +176,7 @@ FILETIME utcToLocal(const FILETIME& utcTime) //throw (std::runtime_error) inline -FILETIME localToUtc(const FILETIME& localTime) //throw (std::runtime_error) +FILETIME localToUtc(const FILETIME& localTime) //throw std::runtime_error { //treat binary local time representation (which is invariant under DST time zone shift) as logical UTC: FILETIME utcTime = {}; @@ -330,7 +329,7 @@ bool dst::fatHasUtcEncoded(const RawTime& rawTime) //"createTimeRaw" as retrieve } -dst::RawTime dst::fatEncodeUtcTime(const FILETIME& writeTimeRealUtc) //throw (std::runtime_error) +dst::RawTime dst::fatEncodeUtcTime(const FILETIME& writeTimeRealUtc) //throw std::runtime_error { const FILETIME fatWriteTimeUtc = roundToFatWriteTime(writeTimeRealUtc); //writeTimeRealUtc may have incompatible precision (NTFS) diff --git a/zen/dst_hack.h b/zen/dst_hack.h index 07d08dc5..600107bb 100644 --- a/zen/dst_hack.h +++ b/zen/dst_hack.h @@ -19,8 +19,6 @@ Solve DST +-1h and time zone shift issues on FAT drives */ bool isFatDrive(const Zstring& fileName); //throw () -bool isFatDrive(HANDLE hFile); //throw() -> call ONLY if vistaOrLater() == true! -bool vistaOrLater(); //all subsequent functions may throw the std::runtime_error exception! @@ -31,10 +29,10 @@ struct RawTime //time as retrieved by ::FindFirstFile() and ::GetFileAttributesE FILETIME writeTimeRaw; }; //save UTC time resistant against DST/time zone shifts -bool fatHasUtcEncoded(const RawTime& rawTime); //as retrieved by ::FindFirstFile() and ::GetFileAttributesEx(); throw (std::runtime_error) +bool fatHasUtcEncoded(const RawTime& rawTime); //throw std::runtime_error; as retrieved by ::FindFirstFile() and ::GetFileAttributesEx() -RawTime fatEncodeUtcTime(const FILETIME& writeTimeRealUtc); //throw (std::runtime_error) -FILETIME fatDecodeUtcTime(const RawTime& rawTime); //return last write time in real UTC; throw (std::runtime_error) +RawTime fatEncodeUtcTime(const FILETIME& writeTimeRealUtc); //throw std::runtime_error +FILETIME fatDecodeUtcTime(const RawTime& rawTime); //throw std::runtime_error; return last write time in real UTC } #endif // DST_HACK_H_INCLUDED diff --git a/zen/file_error.h b/zen/file_error.h index 853267d4..4565d0b7 100644 --- a/zen/file_error.h +++ b/zen/file_error.h @@ -38,9 +38,22 @@ DEFINE_NEW_FILE_ERROR(ErrorFileLocked); //----------- facilitate usage of std::wstring for error messages -------------------- //allow implicit UTF8 conversion: since std::wstring models a GUI string, convenience is more important than performance -inline std::wstring operator+(const std::wstring& lhs, const Zstring& rhs) { return std::wstring(lhs) += zen::utf8CvrtTo<std::wstring>(rhs); } +inline +std::wstring operator+(const std::wstring& lhs, const Zstring& rhs) { return std::wstring(lhs) += utf8CvrtTo<std::wstring>(rhs); } //we musn't put our overloads in namespace std, but namespace zen (+ using directive) is sufficient + + +inline +std::wstring fmtFileName(const Zstring& filename) +{ + std::wstring output; + output.reserve(filename.size() + 2); + output += L'\"'; + output += utf8CvrtTo<std::wstring>(filename); + output += L'\"'; + return output; +} } #endif // FILEERROR_H_INCLUDED diff --git a/zen/file_handling.cpp b/zen/file_handling.cpp index a81b3a80..864fd61f 100644 --- a/zen/file_handling.cpp +++ b/zen/file_handling.cpp @@ -14,7 +14,7 @@ #include "file_io.h" #include "assert_static.h" #include <boost/thread/tss.hpp> -#include <boost/thread/once.hpp> +//#include <boost/thread/once.hpp> #include "file_id_def.h" #ifdef FFS_WIN @@ -26,12 +26,14 @@ #include "dst_hack.h" #include "file_update_handle.h" #include "win_ver.h" +#include "IFileOperation/file_op.h" #elif defined FFS_LINUX #include <sys/stat.h> #include <time.h> #include <utime.h> #include <sys/time.h> +#include <sys/vfs.h> #ifdef HAVE_SELINUX #include <selinux/selinux.h> @@ -107,7 +109,7 @@ void getFileAttrib(const Zstring& filename, FileAttrib& attr, ProcSymlink procSl { const HANDLE searchHandle = ::FindFirstFile(applyLongPathPrefix(filename).c_str(), &fileInfo); if (searchHandle == INVALID_HANDLE_VALUE) - throw FileError(_("Error reading file attributes:") + L"\n\"" + filename + L"\"" + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted()); ::FindClose(searchHandle); } // WIN32_FILE_ATTRIBUTE_DATA sourceAttr = {}; @@ -123,7 +125,7 @@ void getFileAttrib(const Zstring& filename, FileAttrib& attr, ProcSymlink procSl if (!isDirectory && dst::isFatDrive(filename)) //throw() { const dst::RawTime rawTime(fileInfo.ftCreationTime, fileInfo.ftLastWriteTime); - if (dst::fatHasUtcEncoded(rawTime)) //throw (std::runtime_error) + if (dst::fatHasUtcEncoded(rawTime)) //throw std::runtime_error { fileInfo.ftLastWriteTime = dst::fatDecodeUtcTime(rawTime); //return last write time in real UTC, throw (std::runtime_error) ::GetSystemTimeAsFileTime(&fileInfo.ftCreationTime); //real creation time information is not available... @@ -139,17 +141,18 @@ void getFileAttrib(const Zstring& filename, FileAttrib& attr, ProcSymlink procSl const HANDLE hFile = ::CreateFile(applyLongPathPrefix(filename).c_str(), //open handle to target of symbolic link 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - 0, + nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); if (hFile == INVALID_HANDLE_VALUE) - throw FileError(_("Error reading file attributes:") + L"\n\"" + filename + L"\"" + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted()); + ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile)); BY_HANDLE_FILE_INFORMATION fileInfoHnd = {}; if (!::GetFileInformationByHandle(hFile, &fileInfoHnd)) - throw FileError(_("Error reading file attributes:") + L"\n\"" + filename + L"\"" + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted()); attr.fileSize = UInt64(fileInfoHnd.nFileSizeLow, fileInfoHnd.nFileSizeHigh); attr.modificationTime = toTimeT(fileInfoHnd.ftLastWriteTime); @@ -162,7 +165,7 @@ void getFileAttrib(const Zstring& filename, FileAttrib& attr, ProcSymlink procSl :: stat(filename.c_str(), &fileInfo) : ::lstat(filename.c_str(), &fileInfo); if (rv != 0) //follow symbolic links - throw FileError(_("Error reading file attributes:") + L"\n\"" + filename + L"\"" + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted()); attr.fileSize = UInt64(fileInfo.st_size); attr.modificationTime = fileInfo.st_mtime; @@ -187,11 +190,54 @@ Int64 zen::getFileTime(const Zstring& filename, ProcSymlink procSl) //throw File } -namespace +UInt64 zen::getFreeDiskSpace(const Zstring& path) //throw FileError { +#ifdef FFS_WIN + ULARGE_INTEGER bytesFree = {}; + if (!::GetDiskFreeSpaceEx(appendSeparator(path).c_str(), //__in_opt LPCTSTR lpDirectoryName, -> "UNC name [...] must include a trailing backslash, for example, "\\MyServer\MyShare\" + &bytesFree, //__out_opt PULARGE_INTEGER lpFreeBytesAvailable, + nullptr, //__out_opt PULARGE_INTEGER lpTotalNumberOfBytes, + nullptr)) //__out_opt PULARGE_INTEGER lpTotalNumberOfFreeBytes + throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(path)) + L"\n\n" + getLastErrorFormatted()); + + return UInt64(bytesFree.LowPart, bytesFree.HighPart); + +#elif defined FFS_LINUX + struct statfs info = {}; + if (::statfs(path.c_str(), &info) != 0) + throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(path)) + L"\n\n" + getLastErrorFormatted()); + + return UInt64(info.f_bsize) * info.f_bavail; +#endif +} +namespace +{ #ifdef FFS_WIN +//(try to) enhance error messages by showing which processed lock the file +Zstring getLockingProcessNames(const Zstring& filename) //throw(), empty string if none found or error occurred +{ + if (vistaOrLater()) + { + using namespace fileop; + const DllFun<FunType_getLockingProcesses> getLockingProcesses(getDllName(), funName_getLockingProcesses); + const DllFun<FunType_freeString> freeString (getDllName(), funName_freeString); + + if (getLockingProcesses && freeString) + { + const wchar_t* procList = nullptr; + if (getLockingProcesses(filename.c_str(), procList)) + { + ZEN_ON_SCOPE_EXIT(freeString(procList)); + return procList; + } + } + } + return Zstring(); +} + + DWORD retrieveVolumeSerial(const Zstring& pathName) //return 0 on error! { //note: this even works for network shares: \\share\dirname @@ -205,9 +251,7 @@ DWORD retrieveVolumeSerial(const Zstring& pathName) //return 0 on error! BUFFER_SIZE)) //__in DWORD cchBufferLength return 0; - Zstring volumePath = &buffer[0]; - if (!endsWith(volumePath, FILE_NAME_SEPARATOR)) - volumePath += FILE_NAME_SEPARATOR; + Zstring volumePath = appendSeparator(&buffer[0]); DWORD volumeSerial = 0; if (!::GetVolumeInformation(volumePath.c_str(), //__in_opt LPCTSTR lpRootPathName, @@ -257,7 +301,7 @@ zen::ResponseSame zen::onSameVolume(const Zstring& folderLeft, const Zstring& fo } -bool zen::removeFile(const Zstring& filename) //throw FileError; +bool zen::removeFile(const Zstring& filename) //throw FileError { #ifdef FFS_WIN const Zstring& filenameFmt = applyLongPathPrefix(filename); @@ -269,8 +313,11 @@ bool zen::removeFile(const Zstring& filename) //throw FileError; ErrorCode lastError = getLastError(); if (errorCodeForNotExisting(lastError)) //no error situation if file is not existing! manual deletion relies on it! return false; + + const std::wstring shortMsg = replaceCpy(_("Cannot delete file %x."), L"%x", fmtFileName(filename)); + #ifdef FFS_WIN - else if (lastError == ERROR_ACCESS_DENIED) //function fails if file is read-only + if (lastError == ERROR_ACCESS_DENIED) //function fails if file is read-only { ::SetFileAttributes(filenameFmt.c_str(), FILE_ATTRIBUTE_NORMAL); //(try to) normalize file attributes @@ -278,12 +325,20 @@ bool zen::removeFile(const Zstring& filename) //throw FileError; return true; lastError = ::GetLastError(); } + + if (lastError == ERROR_SHARING_VIOLATION || //-> enhance error message! + lastError == ERROR_LOCK_VIOLATION) + { + const Zstring procList = getLockingProcessNames(filename); //throw() + if (!procList.empty()) + throw FileError(shortMsg + L"\n\n" + _("The file is locked by another process:") + L"\n" + procList); + } #endif //after "lastError" evaluation it *may* be redundant to check existence again, but better be safe than sorry: if (!somethingExists(filename)) //warning: changes global error code!! return false; //neither file nor any other object (e.g. broken symlink) with that name existing - throw FileError(_("Error deleting file:") + L"\n\"" + filename + L"\"" + L"\n\n" + getLastErrorFormatted(lastError)); + throw FileError(shortMsg + L"\n\n" + getLastErrorFormatted(lastError)); } return true; } @@ -312,6 +367,17 @@ void renameFile_sub(const Zstring& oldName, const Zstring& newName) //throw File 0)) //__in DWORD dwFlags { DWORD lastError = ::GetLastError(); + + const std::wstring shortMsg = replaceCpy(replaceCpy(_("Cannot move file %x to %y."), L"%x", fmtFileName(oldName)), L"%y", fmtFileName(newName)); + + if (lastError == ERROR_SHARING_VIOLATION || //-> enhance error message! + lastError == ERROR_LOCK_VIOLATION) + { + const Zstring procList = getLockingProcessNames(oldName); //throw() + if (!procList.empty()) + throw FileError(shortMsg + L"\n\n" + _("The file is locked by another process:") + L"\n" + procList); + } + if (lastError == ERROR_ACCESS_DENIED) //MoveFileEx may fail to rename a read-only file on a SAMBA-share -> (try to) handle this { const DWORD oldAttr = ::GetFileAttributes(oldNameFmt.c_str()); @@ -339,11 +405,13 @@ void renameFile_sub(const Zstring& oldName, const Zstring& newName) //throw File } } - std::wstring errorMessage = _("Error moving file:") + L"\n\"" + oldName + L"\" ->\n\"" + newName + L"\"" + L"\n\n" + getLastErrorFormatted(lastError); + std::wstring errorMessage = shortMsg + L"\n\n" + getLastErrorFormatted(lastError); if (lastError == ERROR_NOT_SAME_DEVICE) throw ErrorDifferentVolume(errorMessage); - else if (lastError == ERROR_FILE_EXISTS) + + else if (lastError == ERROR_ALREADY_EXISTS || //-> used on Win7 x64 + lastError == ERROR_FILE_EXISTS) //-> used by XP??? throw ErrorTargetExisting(errorMessage); else throw FileError(errorMessage); @@ -353,8 +421,8 @@ void renameFile_sub(const Zstring& oldName, const Zstring& newName) //throw File if (::rename(oldName.c_str(), newName.c_str()) != 0) { const int lastError = errno; - - std::wstring errorMessage = _("Error moving file:") + L"\n\"" + oldName + L"\" ->\n\"" + newName + L"\"" + L"\n\n" + getLastErrorFormatted(lastError); + std::wstring errorMessage = replaceCpy(replaceCpy(_("Cannot move file %x to %y."), L"%x", fmtFileName(oldName)), L"%y", fmtFileName(newName)) + + L"\n\n" + getLastErrorFormatted(lastError); if (lastError == EXDEV) throw ErrorDifferentVolume(errorMessage); @@ -384,7 +452,7 @@ Zstring getFilenameFmt(const Zstring& filename, Function fun) //throw(); returns const DWORD rv = fun(filenameFmt.c_str(), //__in LPCTSTR lpszShortPath, &buffer[0], //__out LPTSTR lpszLongPath, - static_cast<DWORD>(buffer.size())); //__in DWORD cchBuffer + bufferSize); //__in DWORD cchBuffer if (rv == 0 || rv >= buffer.size()) return Zstring(); @@ -394,7 +462,7 @@ Zstring getFilenameFmt(const Zstring& filename, Function fun) //throw(); returns Zstring findUnused8Dot3Name(const Zstring& filename) //find a unique 8.3 short name { - const Zstring pathPrefix = filename.find(FILE_NAME_SEPARATOR) != Zstring::npos ? + const Zstring pathPrefix = contains(filename, FILE_NAME_SEPARATOR) ? (beforeLast(filename, FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR) : Zstring(); Zstring extension = afterLast(afterLast(filename, FILE_NAME_SEPARATOR), Zchar('.')); //extension needn't contain reasonable data @@ -416,7 +484,7 @@ Zstring findUnused8Dot3Name(const Zstring& filename) //find a unique 8.3 short n bool have8dot3NameClash(const Zstring& filename) { - if (filename.find(FILE_NAME_SEPARATOR) == Zstring::npos) + if (!contains(filename, FILE_NAME_SEPARATOR)) return false; if (somethingExists(filename)) //name OR directory! @@ -479,7 +547,7 @@ void zen::renameFile(const Zstring& oldName, const Zstring& newName) //throw Fil { renameFile_sub(oldName, newName); //throw FileError, ErrorDifferentVolume, ErrorTargetExisting } - catch (const FileError&) + catch (const ErrorTargetExisting&) { #ifdef FFS_WIN //try to handle issues with already existing short 8.3 file names on Windows @@ -501,7 +569,7 @@ class CopyCallbackImpl : public zen::CallbackCopyFile //callback functionality public: CopyCallbackImpl(const Zstring& sourceFile, const Zstring& targetFile, - CallbackMoveFile& callback) : sourceFile_(sourceFile), + CallbackMoveFile* callback) : sourceFile_(sourceFile), targetFile_(targetFile), moveCallback(callback) {} @@ -509,9 +577,12 @@ public: virtual void updateCopyStatus(UInt64 totalBytesTransferred) { - const Int64 delta = to<Int64>(totalBytesTransferred) - bytesReported; - moveCallback.updateStatus(delta); - bytesReported += delta; + if (moveCallback) + { + const Int64 delta = to<Int64>(totalBytesTransferred) - bytesReported; + moveCallback->updateStatus(delta); + bytesReported += delta; + } } private: @@ -520,48 +591,44 @@ private: const Zstring sourceFile_; const Zstring targetFile_; - CallbackMoveFile& moveCallback; + CallbackMoveFile* moveCallback; //optional Int64 bytesReported; }; -void zen::moveFile(const Zstring& sourceFile, const Zstring& targetFile, bool ignoreExisting, CallbackMoveFile* callback) //throw FileError; +void zen::moveFile(const Zstring& sourceFile, const Zstring& targetFile, CallbackMoveFile* callback) //throw FileError { if (callback) callback->onBeforeFileMove(sourceFile, targetFile); //call back once *after* work was done - const bool targetExisting = fileExists(targetFile); - - if (targetExisting && !ignoreExisting) //test file existence: e.g. Linux might silently overwrite existing symlinks - throw FileError(_("Error moving file:") + L"\n\"" + sourceFile + L"\" ->\n\"" + targetFile + L"\"" + - L"\n\n" + _("Target file already existing!")); - - if (!targetExisting) + //first try to move the file directly without copying + try { - //try to move the file directly without copying - try - { - renameFile(sourceFile, targetFile); //throw FileError, ErrorDifferentVolume - //great, we get away cheaply! - if (callback) callback->objectProcessed(); - return; - } - //if moving failed treat as error (except when it tried to move to a different volume: in this case we will copy the file) - catch (const ErrorDifferentVolume&) {} + renameFile(sourceFile, targetFile); //throw FileError, ErrorDifferentVolume, ErrorTargetExisting + //great, we get away cheaply! + if (callback) callback->objectProcessed(); + return; + } + //if moving failed treat as error (except when it tried to move to a different volume: in this case we will copy the file) + catch (const ErrorDifferentVolume&) {} + catch (const ErrorTargetExisting&) {} + //create target + if (!fileExists(targetFile)) //check even if ErrorTargetExisting: me may have clashed with a directory of the same name!!! + { //file is on a different volume: let's copy it if (symlinkExists(sourceFile)) copySymlink(sourceFile, targetFile, false); //throw FileError; don't copy filesystem permissions else { - std::unique_ptr<CopyCallbackImpl> copyCallback(callback ? new CopyCallbackImpl(sourceFile, targetFile, *callback) : nullptr); - copyFile(sourceFile, targetFile, false, true, copyCallback.get()); //throw FileError; + CopyCallbackImpl copyCallback(sourceFile, targetFile, callback); + copyFile(sourceFile, targetFile, false, true, ©Callback); //throw FileError - permissions "false", transactional copy "true" } - - //attention: if copy-operation was cancelled an exception is thrown => sourcefile is not deleted, as we wish! } + //delete source removeFile(sourceFile); //throw FileError - //note: copying file is NOT undone in case of exception: currently this function is called in context of user-defined deletion dir, where this behavior is fine + + //note: newly copied file is NOT deleted in case of exception: currently this function is called in context of user-defined deletion dir, where this behavior is fine if (callback) callback->objectProcessed(); } @@ -570,8 +637,8 @@ namespace class TraverseOneLevel : public zen::TraverseCallback { public: - typedef std::pair<Zstring, Zstring> NamePair; - typedef std::vector<NamePair> NameList; + typedef std::pair<Zstring, Zstring> ShortLongNames; + typedef std::vector<ShortLongNames> NameList; TraverseOneLevel(NameList& files, NameList& dirs) : files_(files), @@ -579,20 +646,20 @@ public: virtual void onFile(const Zchar* shortName, const Zstring& fullName, const FileInfo& details) { - files_.push_back(NamePair(shortName, fullName)); + files_.push_back(std::make_pair(shortName, fullName)); } virtual void onSymlink(const Zchar* shortName, const Zstring& fullName, const SymlinkInfo& details) { if (details.dirLink) - dirs_.push_back(NamePair(shortName, fullName)); + dirs_.push_back(std::make_pair(shortName, fullName)); else - files_.push_back(NamePair(shortName, fullName)); + files_.push_back(std::make_pair(shortName, fullName)); } virtual std::shared_ptr<TraverseCallback> onDir(const Zchar* shortName, const Zstring& fullName) { - dirs_.push_back(NamePair(shortName, fullName)); + dirs_.push_back(std::make_pair(shortName, fullName)); return nullptr; //DON'T traverse into subdirs; moveDirectory works recursively! } @@ -609,54 +676,51 @@ private: struct RemoveCallbackImpl : public CallbackRemoveDir { - RemoveCallbackImpl(CallbackMoveFile& moveCallback) : moveCallback_(moveCallback) {} + RemoveCallbackImpl(CallbackMoveFile* moveCallback) : moveCallback_(moveCallback) {} - virtual void notifyFileDeletion(const Zstring& filename) { moveCallback_.updateStatus(0); } - virtual void notifyDirDeletion (const Zstring& dirname ) { moveCallback_.updateStatus(0); } + virtual void notifyFileDeletion(const Zstring& filename) { if (moveCallback_) moveCallback_->updateStatus(0); } + virtual void notifyDirDeletion (const Zstring& dirname ) { if (moveCallback_) moveCallback_->updateStatus(0); } private: RemoveCallbackImpl(const RemoveCallbackImpl&); RemoveCallbackImpl& operator=(const RemoveCallbackImpl&); - CallbackMoveFile& moveCallback_; + CallbackMoveFile* moveCallback_; //optional }; } -void moveDirectoryImpl(const Zstring& sourceDir, const Zstring& targetDir, bool ignoreExisting, CallbackMoveFile* callback) //throw FileError +void moveDirectoryImpl(const Zstring& sourceDir, const Zstring& targetDir, CallbackMoveFile* callback) //throw FileError { - if (callback) callback->onBeforeDirMove(sourceDir, targetDir); //call back once *after* work was done - - const bool targetExisting = dirExists(targetDir); + //note: we cannot support "throw exception if target already exists": If we did, we would have to do a full cleanup + //removing all newly created directories in case of an exception so that subsequent tries would not fail with "target already existing". + //However an exception may also happen during final deletion of source folder, in which case cleanup effectively leads to data loss! - if (targetExisting && !ignoreExisting) //directory or symlink exists (or even a file... this error will be caught later) - throw FileError(_("Error moving directory:") + L"\n\"" + sourceDir + L"\" ->\n\"" + targetDir + L"\"" + - L"\n\n" + _("Target directory already existing!")); - - const bool isSymlink = symlinkExists(sourceDir); + if (callback) callback->onBeforeDirMove(sourceDir, targetDir); //call back once *after* work was done - if (!targetExisting) + //first try to move the directory directly without copying + try { - //first try to move the directory directly without copying - try - { - renameFile(sourceDir, targetDir); //throw FileError, ErrorDifferentVolume, ErrorTargetExisting - //great, we get away cheaply! - if (callback) callback->objectProcessed(); - return; - } - //if moving failed treat as error (except when it tried to move to a different volume: in this case we will copy the directory) - catch (const ErrorDifferentVolume&) {} + renameFile(sourceDir, targetDir); //throw FileError, ErrorDifferentVolume, ErrorTargetExisting + //great, we get away cheaply! + if (callback) callback->objectProcessed(); + return; + } + //if moving failed treat as error (except when it tried to move to a different volume: in this case we will copy the directory) + catch (const ErrorDifferentVolume&) {} + catch (const ErrorTargetExisting&) {} - //create target - if (isSymlink) + //create target + if (symlinkExists(sourceDir)) + { + if (!dirExists(targetDir)) copySymlink(sourceDir, targetDir, false); //throw FileError -> don't copy permissions - else - createDirectory(targetDir, sourceDir, false); //throw FileError } - - if (!isSymlink) //handle symbolic links + else { + if (!dirExists(targetDir)) //check even if ErrorTargetExisting: me may have clashed with a file of the same name!!! + createDirectory(targetDir, sourceDir, false); //throw FileError + //move files/folders recursively TraverseOneLevel::NameList fileList; //list of names: 1. short 2.long TraverseOneLevel::NameList dirList; // @@ -665,30 +729,26 @@ void moveDirectoryImpl(const Zstring& sourceDir, const Zstring& targetDir, bool TraverseOneLevel traverseCallback(fileList, dirList); traverseFolder(sourceDir, false, traverseCallback); //traverse one level, don't follow symlinks - const Zstring targetDirFormatted = endsWith(targetDir, FILE_NAME_SEPARATOR) ? //ends with path separator - targetDir : - targetDir + FILE_NAME_SEPARATOR; + const Zstring targetDirPf = appendSeparator(targetDir); //move files for (TraverseOneLevel::NameList::const_iterator i = fileList.begin(); i != fileList.end(); ++i) - moveFile(i->second, targetDirFormatted + i->first, ignoreExisting, callback); //throw FileError, ErrorTargetExisting + moveFile(i->second, targetDirPf + i->first, callback); //throw FileError //move directories for (TraverseOneLevel::NameList::const_iterator i = dirList.begin(); i != dirList.end(); ++i) - ::moveDirectoryImpl(i->second, targetDirFormatted + i->first, ignoreExisting, callback); - - //attention: if move-operation was cancelled an exception is thrown => sourceDir is not deleted, as we wish! + ::moveDirectoryImpl(i->second, targetDirPf + i->first, callback); } //delete source - std::unique_ptr<RemoveCallbackImpl> removeCallback(callback ? new RemoveCallbackImpl(*callback) : nullptr); - removeDirectory(sourceDir, removeCallback.get()); //throw FileError; + RemoveCallbackImpl removeCallback(callback); + removeDirectory(sourceDir, &removeCallback); //throw FileError if (callback) callback->objectProcessed(); } -void zen::moveDirectory(const Zstring& sourceDir, const Zstring& targetDir, bool ignoreExisting, CallbackMoveFile* callback) //throw FileError +void zen::moveDirectory(const Zstring& sourceDir, const Zstring& targetDir, CallbackMoveFile* callback) //throw FileError { #ifdef FFS_WIN const Zstring& sourceDirFormatted = sourceDir; @@ -705,7 +765,7 @@ void zen::moveDirectory(const Zstring& sourceDir, const Zstring& targetDir, bool targetDir; #endif - ::moveDirectoryImpl(sourceDirFormatted, targetDirFormatted, ignoreExisting, callback); + ::moveDirectoryImpl(sourceDirFormatted, targetDirFormatted, callback); } @@ -764,7 +824,7 @@ void zen::removeDirectory(const Zstring& directory, CallbackRemoveDir* callback) #elif defined FFS_LINUX if (::unlink(directory.c_str()) != 0) #endif - throw FileError(_("Error deleting directory:") + L"\n\"" + directory + L"\"" + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + getLastErrorFormatted()); if (callback) callback->notifyDirDeletion(directory); //once per symlink @@ -797,7 +857,7 @@ void zen::removeDirectory(const Zstring& directory, CallbackRemoveDir* callback) #else if (::rmdir(directory.c_str()) != 0) #endif - throw FileError(_("Error deleting directory:") + L"\n\"" + directory + L"\"" + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + getLastErrorFormatted()); if (callback) callback->notifyDirDeletion(directory); //and once per folder @@ -813,7 +873,7 @@ void zen::setFileTime(const Zstring& filename, const Int64& modificationTime, Pr //####################################### DST hack ########################################### if (dst::isFatDrive(filename)) //throw() { - const dst::RawTime encodedTime = dst::fatEncodeUtcTime(lastWriteTime); //throw (std::runtime_error) + const dst::RawTime encodedTime = dst::fatEncodeUtcTime(lastWriteTime); //throw std::runtime_error creationTime = encodedTime.createTimeRaw; lastWriteTime = encodedTime.writeTimeRaw; } @@ -833,21 +893,20 @@ void zen::setFileTime(const Zstring& filename, const Int64& modificationTime, Pr */ //may need to remove the readonly-attribute (e.g. FAT usb drives) - FileUpdateHandle targetHandle(filename, [ = ]() + FileUpdateHandle targetHandle(filename, [=] { return ::CreateFile(applyLongPathPrefix(filename).c_str(), - GENERIC_READ | GENERIC_WRITE, //use both when writing over network, see comment in file_io.cpp - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - 0, - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS | //needed to open a directory - (procSl == SYMLINK_DIRECT ? FILE_FLAG_OPEN_REPARSE_POINT : 0), //process symlinks - nullptr); + GENERIC_READ | GENERIC_WRITE, //use both when writing over network, see comment in file_io.cpp + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | //needed to open a directory + (procSl == SYMLINK_DIRECT ? FILE_FLAG_OPEN_REPARSE_POINT : 0), //process symlinks + nullptr); }); if (targetHandle.get() == INVALID_HANDLE_VALUE) - throw FileError(_("Error changing modification time:") + L"\n\"" + filename + L"\"" + L"\n\n" + getLastErrorFormatted()); - + throw FileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted()); /* if (hTarget == INVALID_HANDLE_VALUE && ::GetLastError() == ERROR_SHARING_VIOLATION) ::Sleep(retryInterval); //wait then retry @@ -862,7 +921,7 @@ void zen::setFileTime(const Zstring& filename, const Int64& modificationTime, Pr isNullTime(creationTime) ? nullptr : &creationTime, nullptr, &lastWriteTime)) - throw FileError(_("Error changing modification time:") + L"\n\"" + filename + L"\"" + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted()); #ifndef NDEBUG //dst hack: verify data written if (dst::isFatDrive(filename) && !dirExists(filename)) //throw() @@ -886,7 +945,7 @@ void zen::setFileTime(const Zstring& filename, const Int64& modificationTime, Pr // set new "last write time" if (::utime(filename.c_str(), &newTimes) != 0) - throw FileError(_("Error changing modification time:") + L"\n\"" + filename + L"\"" + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted()); } else { @@ -898,7 +957,7 @@ void zen::setFileTime(const Zstring& filename, const Int64& modificationTime, Pr newTimes[1].tv_usec = 0; if (::lutimes(filename.c_str(), newTimes) != 0) - throw FileError(_("Error changing modification time:") + L"\n\"" + filename + L"\"" + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted()); } #endif } @@ -907,11 +966,12 @@ void zen::setFileTime(const Zstring& filename, const Int64& modificationTime, Pr bool zen::supportsPermissions(const Zstring& dirname) //throw FileError { #ifdef FFS_WIN - std::vector<wchar_t> buffer(MAX_PATH + 1); + const DWORD bufferSize = MAX_PATH + 1; + std::vector<wchar_t> buffer(bufferSize); if (!::GetVolumePathName(dirname.c_str(), //__in LPCTSTR lpszFileName, &buffer[0], //__out LPTSTR lpszVolumePathName, - static_cast<DWORD>(buffer.size()))) //__in DWORD cchBufferLength - throw FileError(_("Error reading file attributes:") + L"\n\"" + dirname + L"\"" + L"\n\n" + getLastErrorFormatted()); + bufferSize)) //__in DWORD cchBufferLength + throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(dirname)) + L"\n\n" + getLastErrorFormatted()); DWORD fsFlags = 0; if (!::GetVolumeInformation(&buffer[0], //__in_opt LPCTSTR lpRootPathName, @@ -922,51 +982,10 @@ bool zen::supportsPermissions(const Zstring& dirname) //throw FileError &fsFlags, //__out_opt LPDWORD lpFileSystemFlags, nullptr, //__out LPTSTR lpFileSystemNameBuffer, 0)) //__in DWORD nFileSystemNameSize - throw FileError(_("Error reading file attributes:") + L"\n\"" + dirname + L"\"" + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(dirname)) + L"\n\n" + getLastErrorFormatted()); return (fsFlags & FILE_PERSISTENT_ACLS) != 0; - - // -> the following approach is *only* working since Windows Vista: - // const HANDLE hDir = ::CreateFile(zen::applyLongPathPrefix(dirname).c_str(), - // 0, - // FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - // nullptr, - // OPEN_EXISTING, - // FILE_FLAG_BACKUP_SEMANTICS, // | FILE_FLAG_OPEN_REPARSE_POINT -> follow symlinks - // nullptr); - // if (hDir == INVALID_HANDLE_VALUE) - // throw FileError(_("Error reading file attributes:") + L"\n\"" + dirname + L"\"" + L"\n\n" + getLastErrorFormatted()); - // ZEN_ON_SCOPE_EXIT(::CloseHandle(hDir)); - // - // //dynamically load windows API function (existing since Windows XP) - // typedef BOOL (WINAPI* GetVolumeInformationByHandleWFun)(HANDLE hFile, - // LPWSTR lpVolumeNameBuffer, - // DWORD nVolumeNameSize, - // LPDWORD lpVolumeSerialNumber, - // LPDWORD lpMaximumComponentLength, - // LPDWORD lpFileSystemFlags, - // LPWSTR lpFileSystemNameBuffer, - // DWORD nFileSystemNameSize); - // - // const SysDllFun<GetVolumeInformationByHandleWFun> getVolumeInformationByHandleW(L"kernel32.dll", "GetVolumeInformationByHandleW"); //available since Windows Vista - // if (!getVolumeInformationByHandleW) - // return true; //Windows XP, 2000 -> do not show this error message - // //throw FileError(rror loading library function+ L"\n\"" + L"GetVolumeInformationByHandleW" + L"\""); - // - // DWORD fileSystemFlags = 0; - // if (!getVolumeInformationByHandleW(hDir, //__in HANDLE hFile, - // nullptr, //__out_opt LPTSTR lpVolumeNameBuffer, - // 0, //__in DWORD nVolumeNameSize, - // nullptr, //__out_opt LPDWORD lpVolumeSerialNumber, - // nullptr, //__out_opt LPDWORD lpMaximumComponentLength, - // &fileSystemFlags, //__out_opt LPDWORD lpFileSystemFlags, - // nullptr, //__out LPTSTR lpFileSystemNameBuffer, - // 0)) //__in DWORD nFileSystemNameSize - // throw FileError(_("Error reading file attributes:") + L"\n\"" + dirname + L"\"" + L"\n\n" + getLastErrorFormatted()); - // - // return (fileSystemFlags & FILE_PERSISTENT_ACLS) != 0; - #elif defined FFS_LINUX return true; #endif @@ -982,12 +1001,12 @@ Zstring getSymlinkTargetPath(const Zstring& symlink) //throw FileError const HANDLE hDir = ::CreateFile(applyLongPathPrefix(symlink).c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - 0, + nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, //needed to open a directory nullptr); if (hDir == INVALID_HANDLE_VALUE) - throw FileError(_("Error resolving symbolic link:") + L"\n\"" + symlink + L"\"" + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(symlink)) + L"\n\n" + getLastErrorFormatted()); ZEN_ON_SCOPE_EXIT(::CloseHandle(hDir)); //dynamically load windows API function @@ -997,7 +1016,7 @@ Zstring getSymlinkTargetPath(const Zstring& symlink) //throw FileError DWORD dwFlags); const SysDllFun<GetFinalPathNameByHandleWFunc> getFinalPathNameByHandle(L"kernel32.dll", "GetFinalPathNameByHandleW"); if (!getFinalPathNameByHandle) - throw FileError(_("Error loading library function:") + L"\n\"" + L"GetFinalPathNameByHandleW" + L"\""); + throw FileError(replaceCpy(_("Cannot find system function %x."), L"%x", L"\"GetFinalPathNameByHandleW\"")); const DWORD BUFFER_SIZE = 10000; std::vector<wchar_t> targetPath(BUFFER_SIZE); @@ -1007,7 +1026,7 @@ Zstring getSymlinkTargetPath(const Zstring& symlink) //throw FileError FILE_NAME_NORMALIZED); //__in DWORD dwFlags if (charsWritten >= BUFFER_SIZE || charsWritten == 0) { - std::wstring errorMessage = _("Error resolving symbolic link:") + L"\n\"" + symlink + L"\""; + std::wstring errorMessage = replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(symlink)); if (charsWritten == 0) errorMessage += L"\n\n" + getLastErrorFormatted(); throw FileError(errorMessage); @@ -1032,7 +1051,7 @@ void copySecurityContext(const Zstring& source, const Zstring& target, ProcSymli errno == EOPNOTSUPP) //extended attributes are not supported by the filesystem return; - throw FileError(_("Error reading security context:") + L"\n\"" + source + L"\"" + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot read security context of %x."), L"%x", fmtFileName(source)) + L"\n\n" + getLastErrorFormatted()); } ZEN_ON_SCOPE_EXIT(::freecon(contextSource)); @@ -1060,7 +1079,7 @@ void copySecurityContext(const Zstring& source, const Zstring& target, ProcSymli ::setfilecon(target.c_str(), contextSource) : ::lsetfilecon(target.c_str(), contextSource); if (rv3 < 0) - throw FileError(_("Error writing security context:") + L"\n\"" + target + L"\"" + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot write security context of %x."), L"%x", fmtFileName(target)) + L"\n\n" + getLastErrorFormatted()); } #endif //HAVE_SELINUX @@ -1070,23 +1089,18 @@ void copyObjectPermissions(const Zstring& source, const Zstring& target, ProcSym { #ifdef FFS_WIN //setting privileges requires admin rights! - try - { - //enable privilege: required to read/write SACL information (only) - activatePrivilege(SE_SECURITY_NAME); //polling allowed... - //Note: trying to copy SACL (SACL_SECURITY_INFORMATION) may return ERROR_PRIVILEGE_NOT_HELD (1314) on Samba shares. This is not due to missing privileges! - //However, this is okay, since copying NTFS permissions doesn't make sense in this case anyway - //enable privilege: required to copy owner information - activatePrivilege(SE_RESTORE_NAME); + //enable privilege: required to read/write SACL information (only) + activatePrivilege(SE_SECURITY_NAME); //throw FileError + //Note: trying to copy SACL (SACL_SECURITY_INFORMATION) may return ERROR_PRIVILEGE_NOT_HELD (1314) on Samba shares. This is not due to missing privileges! + //However, this is okay, since copying NTFS permissions doesn't make sense in this case anyway + + //enable privilege: required to copy owner information + activatePrivilege(SE_RESTORE_NAME); //throw FileError + + //the following privilege may be required according to http://msdn.microsoft.com/en-us/library/aa364399(VS.85).aspx (although not needed nor active in my tests) + activatePrivilege(SE_BACKUP_NAME); //throw FileError - //the following privilege may be required according to http://msdn.microsoft.com/en-us/library/aa364399(VS.85).aspx (although not needed nor active in my tests) - activatePrivilege(SE_BACKUP_NAME); - } - catch (const FileError& e) - { - throw FileError(_("Error copying file permissions:") + L"\n\"" + source + L"\" ->\n\"" + target + L"\"" + L"\n\n" + e.toString()); - } //in contrast to ::SetSecurityInfo(), ::SetFileSecurity() seems to honor the "inherit DACL/SACL" flags //CAVEAT: if a file system does not support ACLs, GetFileSecurity() will return successfully with a *valid* security descriptor containing *no* ACL entries! @@ -1110,7 +1124,7 @@ void copyObjectPermissions(const Zstring& source, const Zstring& target, ProcSym if (bytesNeeded > buffer.size()) buffer.resize(bytesNeeded); else - throw FileError(_("Error copying file permissions:") + L"\n\"" + sourceResolved + L"\" ->\n\"" + targetResolved + L"\"" + L"\n\n" + getLastErrorFormatted() + L" (R)"); + throw FileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtFileName(sourceResolved)) + L"\n\n" + getLastErrorFormatted()); } SECURITY_DESCRIPTOR& secDescr = reinterpret_cast<SECURITY_DESCRIPTOR&>(buffer[0]); @@ -1121,7 +1135,7 @@ void copyObjectPermissions(const Zstring& source, const Zstring& target, ProcSym if (!::GetSecurityDescriptorControl(&secDescr, // __in PSECURITY_DESCRIPTOR pSecurityDescriptor, &secCtrl, // __out PSECURITY_DESCRIPTOR_CONTROL pControl, &ctrlRev)) //__out LPDWORD lpdwRevision - throw FileError(_("Error copying file permissions:") + L"\n\"" + sourceResolved + L"\" ->\n\"" + targetResolved + L"\"" + L"\n\n" + getLastErrorFormatted() + L" (C)"); + throw FileErro } //interesting flags: //#define SE_DACL_PRESENT (0x0004) @@ -1134,7 +1148,7 @@ void copyObjectPermissions(const Zstring& source, const Zstring& target, ProcSym OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION, //__in SECURITY_INFORMATION SecurityInformation, &secDescr)) //__in PSECURITY_DESCRIPTOR pSecurityDescriptor - throw FileError(_("Error copying file permissions:") + L"\n\"" + sourceResolved + L"\" ->\n\"" + targetResolved + L"\"" + L"\n\n" + getLastErrorFormatted() + L" (W)"); + throw FileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(targetResolved)) + L"\n\n" + getLastErrorFormatted()); /* PSECURITY_DESCRIPTOR buffer = nullptr; @@ -1148,12 +1162,12 @@ void copyObjectPermissions(const Zstring& source, const Zstring& target, ProcSym const HANDLE hSource = ::CreateFile(applyLongPathPrefix(source).c_str(), READ_CONTROL | ACCESS_SYSTEM_SECURITY, //ACCESS_SYSTEM_SECURITY required for SACL access FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - 0, + nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | (procSl == SYMLINK_DIRECT ? FILE_FLAG_OPEN_REPARSE_POINT : 0), //FILE_FLAG_BACKUP_SEMANTICS needed to open a directory nullptr); if (hSource == INVALID_HANDLE_VALUE) - throw FileError(_("Error copying file permissions:") + L"\n\"" + source + L"\" ->\n\"" + target + L"\"" + L"\n\n" + getLastErrorFormatted() + L" (OR)"); + throw FileError ZEN_ON_SCOPE_EXIT(::CloseHandle(hSource)); // DWORD rc = ::GetNamedSecurityInfo(const_cast<WCHAR*>(applyLongPathPrefix(source).c_str()), -> does NOT dereference symlinks! @@ -1167,7 +1181,7 @@ void copyObjectPermissions(const Zstring& source, const Zstring& target, ProcSym &sacl, //__out_opt PACL *ppSacl, &buffer); //__out_opt PSECURITY_DESCRIPTOR *ppSecurityDescriptor if (rc != ERROR_SUCCESS) - throw FileError(_("Error copying file permissions:") + L"\n\"" + source + L"\" ->\n\"" + target + L"\"" + L"\n\n" + getLastErrorFormatted(rc) + L" (R)"); + throw FileError ZEN_ON_SCOPE_EXIT(::LocalFree(buffer)); SECURITY_DESCRIPTOR_CONTROL secCtrl = 0; @@ -1176,23 +1190,23 @@ void copyObjectPermissions(const Zstring& source, const Zstring& target, ProcSym if (!::GetSecurityDescriptorControl(buffer, // __in PSECURITY_DESCRIPTOR pSecurityDescriptor, &secCtrl, // __out PSECURITY_DESCRIPTOR_CONTROL pControl, &ctrlRev))//__out LPDWORD lpdwRevision - throw FileError(_("Error copying file permissions:") + L"\n\"" + source + L"\" ->\n\"" + target + L"\"" + L"\n\n" + getLastErrorFormatted(rc) + L" (C)"); + throw FileError } //may need to remove the readonly-attribute - FileUpdateHandle targetHandle(target, [ = ]() + FileUpdateHandle targetHandle(target, [=] { return ::CreateFile(applyLongPathPrefix(target).c_str(), // lpFileName GENERIC_WRITE | WRITE_OWNER | WRITE_DAC | ACCESS_SYSTEM_SECURITY, // dwDesiredAccess: all four seem to be required!!! FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, // dwShareMode - 0, // lpSecurityAttributes + nullptr, // lpSecurityAttributes OPEN_EXISTING, // dwCreationDisposition FILE_FLAG_BACKUP_SEMANTICS | (procSl == SYMLINK_DIRECT ? FILE_FLAG_OPEN_REPARSE_POINT : 0), // dwFlagsAndAttributes nullptr); // hTemplateFile }); if (targetHandle.get() == INVALID_HANDLE_VALUE) - throw FileError(_("Error copying file permissions:") + L"\n\"" + source + L"\" ->\n\"" + target + L"\"" + L"\n\n" + getLastErrorFormatted() + L" (OW)"); + throw FileError SECURITY_INFORMATION secFlags = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION; @@ -1213,7 +1227,7 @@ void copyObjectPermissions(const Zstring& source, const Zstring& target, ProcSym sacl); //__in_opt PACL pSacl if (rc != ERROR_SUCCESS) - throw FileError(_("Error copying file permissions:") + L"\n\"" + source + L"\" ->\n\"" + target + L"\"" + L"\n\n" + getLastErrorFormatted(rc) + L" (W)"); + throw FileError */ #elif defined FFS_LINUX @@ -1225,17 +1239,21 @@ void copyObjectPermissions(const Zstring& source, const Zstring& target, ProcSym struct stat fileInfo = {}; if (procSl == SYMLINK_FOLLOW) { - if (::stat(source.c_str(), &fileInfo) != 0 || - ::chown(target.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0 || // may require admin rights! + if (::stat(source.c_str(), &fileInfo) != 0) + throw FileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtFileName(source)) + L"\n\n" + getLastErrorFormatted()); + + if (::chown(target.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0 || // may require admin rights! ::chmod(target.c_str(), fileInfo.st_mode) != 0) - throw FileError(_("Error copying file permissions:") + L"\n\"" + source + L"\" ->\n\"" + target + L"\"" + L"\n\n" + getLastErrorFormatted() + L" (R)"); + throw FileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(target)) + L"\n\n" + getLastErrorFormatted()); } else { - if (::lstat(source.c_str(), &fileInfo) != 0 || - ::lchown(target.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0 || // may require admin rights! + if (::lstat(source.c_str(), &fileInfo) != 0) + throw FileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtFileName(source)) + L"\n\n" + getLastErrorFormatted()); + + if (::lchown(target.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0 || // may require admin rights! (!symlinkExists(target) && ::chmod(target.c_str(), fileInfo.st_mode) != 0)) //setting access permissions doesn't make sense for symlinks on Linux: there is no lchmod() - throw FileError(_("Error copying file permissions:") + L"\n\"" + source + L"\" ->\n\"" + target + L"\"" + L"\n\n" + getLastErrorFormatted() + L" (W)"); + throw FileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(target)) + L"\n\n" + getLastErrorFormatted()); } #endif } @@ -1255,7 +1273,7 @@ void createDirectory_straight(const Zstring& directory, const Zstring& templateD #endif { if (level != 0) return; - throw FileError(_("Error creating directory:") + L"\n\"" + directory + L"\"" + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot create directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + getLastErrorFormatted()); } if (!templateDir.empty()) @@ -1295,7 +1313,7 @@ void createDirectory_straight(const Zstring& directory, const Zstring& templateD HANDLE hDir = ::CreateFile(applyLongPathPrefix(directory).c_str(), GENERIC_READ | GENERIC_WRITE, //read access required for FSCTL_SET_COMPRESSION FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - 0, + nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); @@ -1319,7 +1337,7 @@ void createDirectory_straight(const Zstring& directory, const Zstring& templateD } } #endif - zen::ScopeGuard guardNewDir = zen::makeGuard([&] { removeDirectory(directory); }); //ensure cleanup: + zen::ScopeGuard guardNewDir = zen::makeGuard([&] { try { removeDirectory(directory); } catch (...) {} }); //ensure cleanup: //enforce copying file permissions: it's advertized on GUI... if (copyFilePermissions) @@ -1404,7 +1422,7 @@ void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool const SysDllFun<CreateSymbolicLinkFunc> createSymbolicLink(L"kernel32.dll", "CreateSymbolicLinkW"); if (!createSymbolicLink) - throw FileError(_("Error loading library function:") + L"\n\"" + L"CreateSymbolicLinkW" + L"\""); + throw FileError(replaceCpy(_("Cannot find system function %x."), L"%x", L"\"CreateSymbolicLinkW\"")); if (!createSymbolicLink(targetLink.c_str(), //__in LPTSTR lpSymlinkFileName, - seems no long path prefix is required... linkPath.c_str(), //__in LPTSTR lpTargetFileName, @@ -1412,17 +1430,22 @@ void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool #elif defined FFS_LINUX if (::symlink(linkPath.c_str(), targetLink.c_str()) != 0) #endif - throw FileError(_("Error copying symbolic link:") + L"\n\"" + sourceLink + L"\" ->\n\"" + targetLink + L"\"" + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."), L"%x", fmtFileName(sourceLink)), L"%y", fmtFileName(targetLink)) + + L"\n\n" + getLastErrorFormatted()); //allow only consistent objects to be created -> don't place before ::symlink, targetLink may already exist zen::ScopeGuard guardNewDir = zen::makeGuard([&] { + try + { #ifdef FFS_WIN - if (isDirLink) - removeDirectory(targetLink); - else + if (isDirLink) + removeDirectory(targetLink); + else #endif - removeFile(targetLink); + removeFile(targetLink); + } + catch (...) {} }); //file times: essential for a symlink: enforce this! (don't just try!) @@ -1440,72 +1463,423 @@ void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool namespace { -Zstring createTempName(const Zstring& filename) +#ifdef FFS_WIN +/* + CopyFileEx() BackupRead() FileRead() + -------------------------------------------- +Attributes YES NO NO +create time NO NO NO +ADS YES YES NO +Encrypted YES NO(silent fail!) NO +Compressed NO NO NO +Sparse NO YES NO +Nonstandard FS YES UNKNOWN -> issues writing ADS to Samba, issues reading from NAS, error copying files having "blocked" state... ect. +PERF - 6% faster + +Mark stream as compressed: FSCTL_SET_COMPRESSION - compatible with both BackupRead() and FileRead() + + +Current support for combinations of NTFS extended attributes: + +source attr | tf normal | tf compressed | tf encrypted | handled by +============|================================================================== + --- | --- -C- E-- copyFileWindowsDefault + --S | --S -CS E-S copyFileWindowsSparse + -C- | --- (NOK) -C- E-- copyFileWindowsDefault + -CS | -CS -CS E-S copyFileWindowsSparse + E-- | E-- E-- E-- copyFileWindowsDefault + E-S | E-- (NOK) E-- (NOK) E-- (NOK) copyFileWindowsDefault -> may fail with ERROR_DISK_FULL!! + +tf := target folder +E := encrypted +C := compressed +S := sparse +NOK := current behavior is not optimal/OK yet. + +Note: - if target parent folder is compressed or encrypted, both attributes are added automatically during file creation! + - "compressed" and "encrypted" are mutually exclusive: http://support.microsoft.com/kb/223093/en-us +*/ + + +//due to issues on non-NTFS volumes, we should use the copy-as-sparse routine only if required and supported! +bool canCopyAsSparse(HANDLE hSource, const Zstring& targetFile) //throw () { - Zstring output = filename + zen::TEMP_FILE_ENDING; + BY_HANDLE_FILE_INFORMATION fileInfoSource = {}; + if (!::GetFileInformationByHandle(hSource, &fileInfoSource)) + return false; - //ensure uniqueness - for (int i = 1; somethingExists(output); ++i) - output = filename + Zchar('_') + numberTo<Zstring>(i) + zen::TEMP_FILE_ENDING; + const bool sourceIsEncrypted = (fileInfoSource.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) != 0; + const bool sourceIsSparse = (fileInfoSource.dwFileAttributes & FILE_ATTRIBUTE_SPARSE_FILE) != 0; - return output; + if (sourceIsEncrypted || !sourceIsSparse) //BackupRead() silently fails reading encrypted files! + return false; //small perf optimization: don't check "targetFile" if not needed + + //------------------------------------------------------------------------------------ + const DWORD bufferSize = 10000; + std::vector<wchar_t> buffer(bufferSize); + + //full pathName need not yet exist! + if (!::GetVolumePathName(targetFile.c_str(), //__in LPCTSTR lpszFileName, + &buffer[0], //__out LPTSTR lpszVolumePathName, + bufferSize)) //__in DWORD cchBufferLength + return false; + + DWORD fsFlagsTarget = 0; + if (!::GetVolumeInformation(&buffer[0], //__in_opt LPCTSTR lpRootPathName + nullptr, //__out_opt LPTSTR lpVolumeNameBuffer, + 0, //__in DWORD nVolumeNameSize, + nullptr, //__out_opt LPDWORD lpVolumeSerialNumber, + nullptr, //__out_opt LPDWORD lpMaximumComponentLength, + &fsFlagsTarget, //__out_opt LPDWORD lpFileSystemFlags, + nullptr, //__out LPTSTR lpFileSystemNameBuffer, + 0)) //__in DWORD nFileSystemNameSize + return false; + + const bool targetSupportSparse = (fsFlagsTarget & FILE_SUPPORTS_SPARSE_FILES) != 0; + + return targetSupportSparse; + //both source and target must not be FAT since copyFileWindowsSparse() does no DST hack! implicitly guaranteed at this point! } -#ifdef FFS_WIN -class CallbackData + +bool canCopyAsSparse(const Zstring& sourceFile, const Zstring& targetFile) //throw () { -public: - CallbackData(CallbackCopyFile* cb, //may be nullptr - const Zstring& sourceFile, - const Zstring& targetFile) : - userCallback(cb), - sourceFile_(sourceFile), - targetFile_(targetFile), - exceptionInUserCallback(false) {} + HANDLE hFileSource = ::CreateFile(applyLongPathPrefix(sourceFile).c_str(), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //all shared modes are required to read files that are open in other applications + nullptr, + OPEN_EXISTING, + 0, + nullptr); + if (hFileSource == INVALID_HANDLE_VALUE) + return false; + ZEN_ON_SCOPE_EXIT(::CloseHandle(hFileSource)); + + return canCopyAsSparse(hFileSource, targetFile); //throw () +} - CallbackCopyFile* userCallback; //optional! - const Zstring& sourceFile_; - const Zstring& targetFile_; - //there is mixed responsibility in this class, pure read-only data and abstraction for error reporting - //however we need to keep it together as ::CopyFileEx() requires! +//precondition: canCopyAsSparse() must return "true"! +void copyFileWindowsSparse(const Zstring& sourceFile, + const Zstring& targetFile, + CallbackCopyFile* callback, + FileAttrib* newAttrib) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked +{ + assert(canCopyAsSparse(sourceFile, targetFile)); + + //comment suggests "FILE_FLAG_BACKUP_SEMANTICS + SE_BACKUP_NAME" may be needed: http://msdn.microsoft.com/en-us/library/windows/desktop/aa362509(v=vs.85).aspx + try { activatePrivilege(SE_BACKUP_NAME); } + catch (const FileError&) {} + try { activatePrivilege(SE_RESTORE_NAME); } + catch (const FileError&) {} + + //open sourceFile for reading + HANDLE hFileSource = ::CreateFile(applyLongPathPrefix(sourceFile).c_str(), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //all shared modes are required to read files that are open in other applications + nullptr, + OPEN_EXISTING, //FILE_FLAG_OVERLAPPED must not be used! + FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_BACKUP_SEMANTICS, //FILE_FLAG_NO_BUFFERING should not be used! + nullptr); + if (hFileSource == INVALID_HANDLE_VALUE) + { + const DWORD lastError = ::GetLastError(); + + const std::wstring shortMsg = replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(sourceFile)); + + //if file is locked throw "ErrorFileLocked" instead! + if (lastError == ERROR_SHARING_VIOLATION || + lastError == ERROR_LOCK_VIOLATION) + { + const Zstring procList = getLockingProcessNames(sourceFile); //throw() + throw ErrorFileLocked(shortMsg + L"\n\n" + (!procList.empty() ? _("The file is locked by another process:") + L"\n" + procList : getLastErrorFormatted(lastError))); + } - void reportUserException(const UInt64& bytesTransferred) + throw FileError(shortMsg + L"\n\n" + getLastErrorFormatted(lastError) + L" (open)"); + } + ZEN_ON_SCOPE_EXIT(::CloseHandle(hFileSource)); + + //---------------------------------------------------------------------- + BY_HANDLE_FILE_INFORMATION fileInfoSource = {}; + if (!::GetFileInformationByHandle(hFileSource, &fileInfoSource)) + throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(sourceFile)) + L"\n\n" + getLastErrorFormatted()); + + //---------------------------------------------------------------------- + const DWORD validAttribs = FILE_ATTRIBUTE_READONLY | + FILE_ATTRIBUTE_HIDDEN | + FILE_ATTRIBUTE_SYSTEM | + FILE_ATTRIBUTE_ARCHIVE | //those two are not set properly (not worse than ::CopyFileEx()) + FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; // + //FILE_ATTRIBUTE_ENCRYPTED -> no! + + //create targetFile and open it for writing + HANDLE hFileTarget = ::CreateFile(applyLongPathPrefix(targetFile).c_str(), + GENERIC_READ | GENERIC_WRITE, //read access required for FSCTL_SET_COMPRESSION + FILE_SHARE_READ | FILE_SHARE_DELETE, + nullptr, + CREATE_NEW, + FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_BACKUP_SEMANTICS | (fileInfoSource.dwFileAttributes & validAttribs), + //FILE_FLAG_OVERLAPPED must not be used! FILE_FLAG_NO_BUFFERING should not be used! + nullptr); + if (hFileTarget == INVALID_HANDLE_VALUE) { - exceptionInUserCallback = true; - bytesTransferredOnException = bytesTransferred; + const DWORD lastError = ::GetLastError(); + const std::wstring errorMessage = replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(targetFile)) + L"\n\n" + getLastErrorFormatted(lastError) + L" (open)"; + + if (lastError == ERROR_FILE_EXISTS || //confirmed to be used + lastError == ERROR_ALREADY_EXISTS) //comment on msdn claims, this one is used on Windows Mobile 6 + throw ErrorTargetExisting(errorMessage); + + if (lastError == ERROR_PATH_NOT_FOUND) + throw ErrorTargetPathMissing(errorMessage); + + throw FileError(errorMessage); } + ScopeGuard guardTarget = makeGuard([&] { try { removeFile(targetFile); } catch (...) {} }); //transactional behavior: guard just after opening target and before managing hFileOut + ZEN_ON_SCOPE_EXIT(::CloseHandle(hFileTarget)); - void reportError(const std::wstring& message) { errorMsg = message; } + //---------------------------------------------------------------------- + BY_HANDLE_FILE_INFORMATION fileInfoTarget = {}; + if (!::GetFileInformationByHandle(hFileTarget, &fileInfoTarget)) + throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(targetFile)) + L"\n\n" + getLastErrorFormatted()); - void evaluateErrors() //throw + //return up-to-date file attributes + if (newAttrib) { - if (exceptionInUserCallback) + newAttrib->fileSize = UInt64(fileInfoSource.nFileSizeLow, fileInfoSource.nFileSizeHigh); + newAttrib->modificationTime = toTimeT(fileInfoSource.ftLastWriteTime); //no DST hack (yet) + newAttrib->sourceFileId = extractFileID(fileInfoSource); + newAttrib->targetFileId = extractFileID(fileInfoTarget); + } + + //---------------------------------------------------------------------- + DWORD fsFlagsTarget = 0; + { + const DWORD bufferSize = 10000; + std::vector<wchar_t> buffer(bufferSize); + + //full pathName need not yet exist! + if (!::GetVolumePathName(targetFile.c_str(), //__in LPCTSTR lpszFileName, + &buffer[0], //__out LPTSTR lpszVolumePathName, + bufferSize)) //__in DWORD cchBufferLength + throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(targetFile)) + L"\n\n" + getLastErrorFormatted()); + + //GetVolumeInformationByHandleW would be a better solution, but it is supported beginning with Vista only! + if (!::GetVolumeInformation(&buffer[0], //__in_opt LPCTSTR lpRootPathName + nullptr, //__out_opt LPTSTR lpVolumeNameBuffer, + 0, //__in DWORD nVolumeNameSize, + nullptr, //__out_opt LPDWORD lpVolumeSerialNumber, + nullptr, //__out_opt LPDWORD lpMaximumComponentLength, + &fsFlagsTarget, //__out_opt LPDWORD lpFileSystemFlags, + nullptr, //__out LPTSTR lpFileSystemNameBuffer, + 0)) //__in DWORD nFileSystemNameSize + throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(targetFile)) + L"\n\n" + getLastErrorFormatted()); + } + + //---------------------------------------------------------------------- + const bool sourceIsCompressed = (fileInfoSource.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; + const bool sourceIsSparse = (fileInfoSource.dwFileAttributes & FILE_ATTRIBUTE_SPARSE_FILE) != 0; + + const bool targetSupportsCompressed = (fsFlagsTarget & FILE_FILE_COMPRESSION ) != 0; + const bool targetSupportsSparse = (fsFlagsTarget & FILE_SUPPORTS_SPARSE_FILES) != 0; + + //---------------------------------------------------------------------- + if (sourceIsCompressed && targetSupportsCompressed) + { + USHORT cmpState = COMPRESSION_FORMAT_DEFAULT; + DWORD bytesReturned = 0; + if (!DeviceIoControl(hFileTarget, //handle to file or directory + FSCTL_SET_COMPRESSION, //dwIoControlCode + &cmpState, //input buffer + sizeof(cmpState), //size of input buffer + nullptr, //lpOutBuffer + 0, //OutBufferSize + &bytesReturned, //number of bytes returned + nullptr)) //OVERLAPPED structure { - assert(userCallback); - if (userCallback) - userCallback->updateCopyStatus(bytesTransferredOnException); //rethrow (hopefully!) + //-> if target folder is encrypted this call will legitimately fail with ERROR_INVALID_FUNCTION + //throw FileError } - if (!errorMsg.empty()) - throw FileError(errorMsg); } - void setNewAttr(const FileAttrib& attr) { newAttrib = attr; } + //although it seems the sparse attribute is set automatically by BackupWrite, we are required to do this manually: http://support.microsoft.com/kb/271398/en-us + //Quote: It is the responsibility of the backup utility to apply file attributes to a file after it is restored by using BackupWrite. + //The application should retrieve the attributes by using GetFileAttributes prior to creating a backup with BackupRead. + //If a file originally had the sparse attribute (FILE_ATTRIBUTE_SPARSE_FILE), the backup utility must explicitly set the + //attribute on the restored file. The attribute can be set by using the DeviceIoControl function with the FSCTL_SET_SPARSE flag. + + if (sourceIsSparse && targetSupportsSparse) + { + DWORD bytesReturned = 0; + if (!DeviceIoControl(hFileTarget, //handle to file + FSCTL_SET_SPARSE, //dwIoControlCode + nullptr, //input buffer + 0, //size of input buffer + nullptr, //lpOutBuffer + 0, //OutBufferSize + &bytesReturned, //number of bytes returned + nullptr)) //OVERLAPPED structure + throw FileError(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtFileName(targetFile)) + + L"\n\n" + zen::getLastErrorFormatted() + L" (NTFS sparse)"); + } + + //---------------------------------------------------------------------- + const DWORD BUFFER_SIZE = 512 * 1024; //512 kb seems to be a reasonable buffer size - must be greater than sizeof(WIN32_STREAM_ID) + static boost::thread_specific_ptr<std::vector<BYTE>> cpyBuf; + if (!cpyBuf.get()) + cpyBuf.reset(new std::vector<BYTE>(BUFFER_SIZE)); + std::vector<BYTE>& buffer = *cpyBuf; + + LPVOID contextRead = nullptr; //manage context for BackupRead()/BackupWrite() + LPVOID contextWrite = nullptr; // + + ZEN_ON_SCOPE_EXIT( + if (contextRead ) ::BackupRead (0, nullptr, 0, nullptr, true, false, &contextRead); //lpContext must be passed [...] all other parameters are ignored. + if (contextWrite) ::BackupWrite(0, nullptr, 0, nullptr, true, false, &contextWrite); ); + + + //stream-copy sourceFile to targetFile + UInt64 totalBytesTransferred; //may be larger than file size! context information + ADS! + bool eof = false; + do + { + DWORD bytesRead = 0; + if (!::BackupRead(hFileSource, //__in HANDLE hFile, + &buffer[0], //__out LPBYTE lpBuffer, + BUFFER_SIZE, //__in DWORD nNumberOfBytesToRead, + &bytesRead, //__out LPDWORD lpNumberOfBytesRead, + false, //__in BOOL bAbort, + false, //__in BOOL bProcessSecurity, + &contextRead)) //__out LPVOID *lpContext + throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(sourceFile)) + L"\n\n" + getLastErrorFormatted()); //better use fine-granular error messages "reading/writing"! + + if (bytesRead > BUFFER_SIZE) + throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(sourceFile)) + L"\n\n" + L"(buffer overflow)"); + + if (bytesRead < BUFFER_SIZE) + eof = true; + + DWORD bytesWritten = 0; + if (!::BackupWrite(hFileTarget, //__in HANDLE hFile, + &buffer[0], //__in LPBYTE lpBuffer, + bytesRead, //__in DWORD nNumberOfBytesToWrite, + &bytesWritten, //__out LPDWORD lpNumberOfBytesWritten, + false, //__in BOOL bAbort, + false, //__in BOOL bProcessSecurity, + &contextWrite)) //__out LPVOID *lpContext + throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(targetFile)) + L"\n\n" + getLastErrorFormatted()); + + if (bytesWritten != bytesRead) + throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(targetFile)) + L"\n\n" + L"(incomplete write)"); + + totalBytesTransferred += bytesRead; + + //invoke callback method to update progress indicators + if (callback) + callback->updateCopyStatus(totalBytesTransferred); //throw X! + } + while (!eof); + + //DST hack not required, since both source and target volumes cannot be FAT! + + //::BackupRead() silently fails reading encrypted files -> double check! + if (totalBytesTransferred == 0U && UInt64(fileInfoSource.nFileSizeLow, fileInfoSource.nFileSizeHigh) != 0U) + //note: there is no guaranteed ordering relation beween bytes transferred and file size! Consider ADS (>) and compressed/sparse files (<)! + throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(sourceFile)) + L"\n\n" + L"(unknown error)"); + + //time needs to be set at the end: BackupWrite() changes modification time + if (!::SetFileTime(hFileTarget, + &fileInfoSource.ftCreationTime, + nullptr, + &fileInfoSource.ftLastWriteTime)) + throw FileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(targetFile)) + L"\n\n" + getLastErrorFormatted()); + + guardTarget.dismiss(); + + /* + //create sparse file for testing: + HANDLE hSparse = ::CreateFile(L"C:\\sparse.file", + GENERIC_READ | GENERIC_WRITE, //read access required for FSCTL_SET_COMPRESSION + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, + CREATE_NEW, + FILE_FLAG_SEQUENTIAL_SCAN, + nullptr); + if (hFileTarget == INVALID_HANDLE_VALUE) + throw 1; + ZEN_ON_SCOPE_EXIT(::CloseHandle(hSparse)); + + DWORD br = 0; + if (!::DeviceIoControl(hSparse, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &br,nullptr)) + throw 1; + + LARGE_INTEGER liDistanceToMove = {}; + liDistanceToMove.QuadPart = 1024 * 1024 * 1024; //create 5 TB sparse file + liDistanceToMove.QuadPart *= 5 * 1024; //maximum file size on NTFS: 16 TB - 64 kB + if (!::SetFilePointerEx(hSparse, liDistanceToMove, nullptr, FILE_BEGIN)) + throw 1; + + if (!SetEndOfFile(hSparse)) + throw 1; + + FILE_ZERO_DATA_INFORMATION zeroInfo = {}; + zeroInfo.BeyondFinalZero.QuadPart = liDistanceToMove.QuadPart; + if (!::DeviceIoControl(hSparse, FSCTL_SET_ZERO_DATA, &zeroInfo, sizeof(zeroInfo), nullptr, 0, &br, nullptr)) + throw 1; + */ +} + + +DEFINE_NEW_FILE_ERROR(ErrorShouldCopyAsSparse); + +class ErrorHandling +{ +public: + ErrorHandling() : shouldCopyAsSparse(false) {} - FileAttrib getSrcAttr() const + void reportUserException(CallbackCopyFile& userCallback, const UInt64& bytesTransferred) { - assert(newAttrib.modificationTime != 0); - return newAttrib; + exceptionInUserCallback.reset(new std::pair<CallbackCopyFile*, UInt64>(&userCallback, bytesTransferred)); + } + + void reportErrorShouldCopyAsSparse() { shouldCopyAsSparse = true; } + + void reportError(const std::wstring& message) { errorMsg = message; } + + void evaluateErrors() //throw X + { + if (shouldCopyAsSparse) + throw ErrorShouldCopyAsSparse(L"sparse dummy value"); + + if (exceptionInUserCallback) + exceptionInUserCallback->first->updateCopyStatus(exceptionInUserCallback->second); //rethrow (hopefully!) + + if (!errorMsg.empty()) + throw FileError(errorMsg); } private: - CallbackData(const CallbackData&); - CallbackData& operator=(const CallbackData&); + bool shouldCopyAsSparse; + std::wstring errorMsg; //these two are exclusive! + std::unique_ptr<std::pair<CallbackCopyFile*, UInt64>> exceptionInUserCallback; // +}; + + +struct CallbackData +{ + CallbackData(CallbackCopyFile* cb, //may be nullptr + const Zstring& sourceFile, + const Zstring& targetFile) : + sourceFile_(sourceFile), + targetFile_(targetFile), + userCallback(cb) {} + + const Zstring& sourceFile_; + const Zstring& targetFile_; - FileAttrib newAttrib; - std::wstring errorMsg; // - bool exceptionInUserCallback; //these two are exclusive! - UInt64 bytesTransferredOnException; + CallbackCopyFile* const userCallback; //optional! + ErrorHandling errorHandler; + FileAttrib newAttrib; //modified by CopyFileEx at start }; @@ -1541,42 +1915,34 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize, if (dwCallbackReason == CALLBACK_STREAM_SWITCH && //called up-front for every file (even if 0-sized) dwStreamNumber == 1) //consider ADS! { + if (canCopyAsSparse(hSourceFile, cbd.targetFile_)) //throw () + { + cbd.errorHandler.reportErrorShouldCopyAsSparse(); //use another copy routine! + return PROGRESS_CANCEL; + } + //#################### return source file attributes ################################ BY_HANDLE_FILE_INFORMATION fileInfoSrc = {}; if (!::GetFileInformationByHandle(hSourceFile, &fileInfoSrc)) { - cbd.reportError(_("Error reading file attributes:") + L"\n\"" + cbd.sourceFile_ + L"\"" + L"\n\n" + getLastErrorFormatted()); + cbd.errorHandler.reportError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(cbd.sourceFile_)) + L"\n\n" + getLastErrorFormatted()); return PROGRESS_CANCEL; } BY_HANDLE_FILE_INFORMATION fileInfoTrg = {}; if (!::GetFileInformationByHandle(hDestinationFile, &fileInfoTrg)) { - cbd.reportError(_("Error reading file attributes:") + L"\n\"" + cbd.targetFile_ + L"\"" + L"\n\n" + getLastErrorFormatted()); + cbd.errorHandler.reportError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(cbd.targetFile_)) + L"\n\n" + getLastErrorFormatted()); return PROGRESS_CANCEL; } - FileAttrib attr; - attr.fileSize = UInt64(fileInfoSrc.nFileSizeLow, fileInfoSrc.nFileSizeHigh); - attr.modificationTime = toTimeT(fileInfoSrc.ftLastWriteTime); //no DST hack (yet) - attr.sourceFileId = extractFileID(fileInfoSrc); - attr.targetFileId = extractFileID(fileInfoTrg); - - cbd.setNewAttr(attr); + cbd.newAttrib.fileSize = UInt64(fileInfoSrc.nFileSizeLow, fileInfoSrc.nFileSizeHigh); + cbd.newAttrib.modificationTime = toTimeT(fileInfoSrc.ftLastWriteTime); //no DST hack (yet) + cbd.newAttrib.sourceFileId = extractFileID(fileInfoSrc); + cbd.newAttrib.targetFileId = extractFileID(fileInfoTrg); //#################### copy file creation time ################################ - FILETIME creationTime = {}; - - if (!::GetFileTime(hSourceFile, //__in HANDLE hFile, - &creationTime, //__out_opt LPFILETIME lpCreationTime, - nullptr, //__out_opt LPFILETIME lpLastAccessTime, - nullptr)) //__out_opt LPFILETIME lpLastWriteTime - { - cbd.reportError(_("Error reading file attributes:") + L"\n\"" + cbd.sourceFile_ + L"\"" + L"\n\n" + getLastErrorFormatted()); - return PROGRESS_CANCEL; - } - - ::SetFileTime(hDestinationFile, &creationTime, nullptr, nullptr); //no error handling! + ::SetFileTime(hDestinationFile, &fileInfoSrc.ftCreationTime, nullptr, nullptr); //no error handling! //############################################################################## } @@ -1594,13 +1960,13 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize, nullptr, 0); try { - cbd.userCallback->updateCopyStatus(UInt64(totalBytesTransferred.QuadPart)); + cbd.userCallback->updateCopyStatus(UInt64(totalBytesTransferred.QuadPart)); //throw X! } catch (...) { //#warning migrate to std::exception_ptr when available - cbd.reportUserException(UInt64(totalBytesTransferred.QuadPart)); + cbd.errorHandler.reportUserException(*cbd.userCallback, UInt64(totalBytesTransferred.QuadPart)); return PROGRESS_CANCEL; } } @@ -1608,56 +1974,68 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize, } -#ifndef COPY_FILE_ALLOW_DECRYPTED_DESTINATION -#define COPY_FILE_ALLOW_DECRYPTED_DESTINATION 0x00000008 -#endif +const bool supportNonEncryptedDestination = winXpOrLater(); //encrypted destination is not supported with Windows 2000 +const bool supportUnbufferedCopy = vistaOrLater(); +//caveat: function scope static initialization is not thread-safe in VS 2010! -void rawCopyWinApi_sub(const Zstring& sourceFile, - const Zstring& targetFile, - CallbackCopyFile* callback, - FileAttrib* newAttrib) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked + +void copyFileWindowsDefault(const Zstring& sourceFile, + const Zstring& targetFile, + CallbackCopyFile* callback, + FileAttrib* newAttrib) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked, ErrorShouldCopyAsSparse { zen::ScopeGuard guardTarget = zen::makeGuard([&] { try { removeFile(targetFile); } catch (...) {} }); //transactional behavior: guard just before starting copy, we don't trust ::CopyFileEx(), do we? ;) DWORD copyFlags = COPY_FILE_FAIL_IF_EXISTS; - //allow copying from encrypted to non-encrytped location - static bool nonEncSupported = false; - { - static boost::once_flag initNonEncOnce = BOOST_ONCE_INIT; //caveat: function scope static initialization is not thread-safe in VS 2010! - boost::call_once(initNonEncOnce, [] { nonEncSupported = winXpOrLater(); }); //encrypted destination is not supported with Windows 2000 - } - if (nonEncSupported) - copyFlags |= COPY_FILE_ALLOW_DECRYPTED_DESTINATION; +#ifndef COPY_FILE_ALLOW_DECRYPTED_DESTINATION + const DWORD COPY_FILE_ALLOW_DECRYPTED_DESTINATION = 0x00000008; +#endif + + if (supportNonEncryptedDestination) + copyFlags |= COPY_FILE_ALLOW_DECRYPTED_DESTINATION; //allow copying from encrypted to non-encrytped location + + if (supportUnbufferedCopy) //see http://blogs.technet.com/b/askperf/archive/2007/05/08/slow-large-file-copy-issues.aspx + copyFlags |= COPY_FILE_NO_BUFFERING; //no perf difference at worst, huge improvement for large files (20% in test NTFS -> NTFS) CallbackData cbd(callback, sourceFile, targetFile); const bool success = ::CopyFileEx( //same performance like CopyFile() - applyLongPathPrefix(sourceFile).c_str(), - applyLongPathPrefix(targetFile).c_str(), - copyCallbackInternal, - &cbd, - nullptr, - copyFlags) == TRUE; //silence x64 perf warning - - cbd.evaluateErrors(); //throw ?, process errors in callback first! + applyLongPathPrefix(sourceFile).c_str(), //__in LPCTSTR lpExistingFileName, + applyLongPathPrefix(targetFile).c_str(), //__in LPCTSTR lpNewFileName, + copyCallbackInternal, //__in_opt LPPROGRESS_ROUTINE lpProgressRoutine, + &cbd, //__in_opt LPVOID lpData, + nullptr, //__in_opt LPBOOL pbCancel, + copyFlags) == TRUE; //__in DWORD dwCopyFlags + + cbd.errorHandler.evaluateErrors(); //throw ?, process errors in callback first! if (!success) { const DWORD lastError = ::GetLastError(); //don't suppress "lastError == ERROR_REQUEST_ABORTED": a user aborted operation IS an error condition! + //trying to copy huge sparse files may fail with ERROR_DISK_FULL + if (canCopyAsSparse(sourceFile, targetFile)) //throw () + throw ErrorShouldCopyAsSparse(L"sparse dummy value2"); + //assemble error message... - std::wstring errorMessage = _("Error copying file:") + L"\n\"" + sourceFile + L"\" ->\n\"" + targetFile + L"\"" + + std::wstring errorMessage = replaceCpy(replaceCpy(_("Cannot copy file %x to %y."), L"%x", fmtFileName(sourceFile)), L"%y", fmtFileName(targetFile)) + L"\n\n" + getLastErrorFormatted(lastError); - //if file is locked (try to) use Windows Volume Shadow Copy Service + //if file is locked throw "ErrorFileLocked" instead! if (lastError == ERROR_SHARING_VIOLATION || lastError == ERROR_LOCK_VIOLATION) - throw ErrorFileLocked(errorMessage); + { + const Zstring procList = getLockingProcessNames(sourceFile); //throw() -> enhance error message! + throw ErrorFileLocked(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(sourceFile)) + L"\n\n" + + (!procList.empty() ? _("The file is locked by another process:") + L"\n" + procList : getLastErrorFormatted(lastError))); + } - if (lastError == ERROR_FILE_EXISTS) //if target is existing this functions is expected to throw ErrorTargetExisting!!! + //if target is existing this functions is expected to throw ErrorTargetExisting!!! + if (lastError == ERROR_FILE_EXISTS || //confirmed to be used + lastError == ERROR_ALREADY_EXISTS) //not sure if used -> better be safe than sorry!!! { guardTarget.dismiss(); //don't delete file that existed previously! throw ErrorTargetExisting(errorMessage); @@ -1685,7 +2063,7 @@ void rawCopyWinApi_sub(const Zstring& sourceFile, } if (newAttrib) - *newAttrib = cbd.getSrcAttr(); + *newAttrib = cbd.newAttrib; { //DST hack @@ -1703,358 +2081,44 @@ void rawCopyWinApi_sub(const Zstring& sourceFile, } +//another layer to support copying sparse files inline -void rawCopyWinApi(const Zstring& sourceFile, - const Zstring& targetFile, - CallbackCopyFile* callback, - FileAttrib* sourceAttr) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked +void copyFileWindowsSelectRoutine(const Zstring& sourceFile, const Zstring& targetFile, CallbackCopyFile* callback, FileAttrib* sourceAttr) { try { - rawCopyWinApi_sub(sourceFile, targetFile, callback, sourceAttr); // throw ... + copyFileWindowsDefault(sourceFile, targetFile, callback, sourceAttr); //throw ErrorShouldCopyAsSparse et al. } - catch (ErrorTargetExisting&) + catch (ErrorShouldCopyAsSparse&) //we cheaply check for this condition within callback of ::CopyFileEx()! + { + copyFileWindowsSparse(sourceFile, targetFile, callback, sourceAttr); + } +} + + +//another layer of indirection solving 8.3 name clashes +inline +void copyFileWindows(const Zstring& sourceFile, const Zstring& targetFile, CallbackCopyFile* callback, FileAttrib* sourceAttr) +{ + try + { + copyFileWindowsSelectRoutine(sourceFile, targetFile, callback, sourceAttr); //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked + } + catch (const ErrorTargetExisting&) { //try to handle issues with already existing short 8.3 file names on Windows if (have8dot3NameClash(targetFile)) { Fix8Dot3NameClash dummy(targetFile); //move clashing filename to the side - rawCopyWinApi_sub(sourceFile, targetFile, callback, sourceAttr); //throw FileError; the short filename name clash is solved, this should work now + copyFileWindowsSelectRoutine(sourceFile, targetFile, callback, sourceAttr); //throw FileError; the short filename name clash is solved, this should work now return; } throw; } } -//void rawCopyWinOptimized(const Zstring& sourceFile, -// const Zstring& targetFile, -// CallbackCopyFile* callback) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked -//{ -// /* -// BackupRead() FileRead() CopyFileEx() -// -------------------------------------------- -// Attributes NO NO YES -// create time NO NO NO -// ADS YES NO YES -// Encrypted NO(silent fail) NO YES -// Compressed NO NO NO -// Sparse YES NO NO -// PERF 6% faster - -// -// Mark stream as compressed: FSCTL_SET_COMPRESSION -// compatible with: BackupRead() FileRead() -// */ -// -//FILE_FLAG_BACKUP_SEMANTICS ?????? -// -// //open sourceFile for reading -// HANDLE hFileIn = ::CreateFile(applyLongPathPrefix(sourceFile).c_str(), -// GENERIC_READ, -// FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //all shared modes are required to read files that are open in other applications -// nullptr, -// OPEN_EXISTING, -// FILE_FLAG_SEQUENTIAL_SCAN, -// nullptr); -// if (hFileIn == INVALID_HANDLE_VALUE) -// { -// const DWORD lastError = ::GetLastError(); -// const std::wstring& errorMessage = Error opening file: + "\n\"" + sourceFile + "\"" + "\n\n" + getLastErrorFormatted(lastError); -// -// //if file is locked (try to) use Windows Volume Shadow Copy Service -// if (lastError == ERROR_SHARING_VIOLATION || -// lastError == ERROR_LOCK_VIOLATION) -// throw ErrorFileLocked(errorMessage); -// -// throw FileError(errorMessage); -// } -// ZEN_ON_SCOPE_EXIT(::CloseHandle, hFileIn); -// -// -// BY_HANDLE_FILE_INFORMATION infoFileIn = {}; -// if (!::GetFileInformationByHandle(hFileIn, &infoFileIn)) -// throw FileError(Error reading file attributes:") + "\n\"" + sourceFile + "\"" + "\n\n" + getLastErrorFormatted()); -// -// //####################################### DST hack ########################################### -// if (dst::isFatDrive(sourceFile)) //throw() -// { -// const dst::RawTime rawTime(infoFileIn.ftCreationTime, infoFileIn.ftLastWriteTime); -// if (dst::fatHasUtcEncoded(rawTime)) //throw (std::runtime_error) -// { -// infoFileIn.ftLastWriteTime = dst::fatDecodeUtcTime(rawTime); //return last write time in real UTC, throw (std::runtime_error) -// ::GetSystemTimeAsFileTime(&infoFileIn.ftCreationTime); //real creation time information is not available... -// } -// } -// -// if (dst::isFatDrive(targetFile)) //throw() -// { -// const dst::RawTime encodedTime = dst::fatEncodeUtcTime(infoFileIn.ftLastWriteTime); //throw (std::runtime_error) -// infoFileIn.ftCreationTime = encodedTime.createTimeRaw; -// infoFileIn.ftLastWriteTime = encodedTime.writeTimeRaw; -// } -// //####################################### DST hack ########################################### -// -// const DWORD validAttribs = FILE_ATTRIBUTE_READONLY | -// FILE_ATTRIBUTE_HIDDEN | -// FILE_ATTRIBUTE_SYSTEM | -// FILE_ATTRIBUTE_ARCHIVE | //those two are not set properly (not worse than ::CopyFileEx()) -// FILE_ATTRIBUTE_NOT_CONTENT_INDEXED | // -// FILE_ATTRIBUTE_ENCRYPTED; -// -// //create targetFile and open it for writing -// HANDLE hFileOut = ::CreateFile(applyLongPathPrefix(targetFile).c_str(), -// GENERIC_READ | GENERIC_WRITE, //read access required for FSCTL_SET_COMPRESSION -// FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, -// nullptr, -// CREATE_NEW, -// (infoFileIn.dwFileAttributes & validAttribs) | FILE_FLAG_SEQUENTIAL_SCAN, -// nullptr); -// if (hFileOut == INVALID_HANDLE_VALUE) -// { -// const DWORD lastError = ::GetLastError(); -// const std::wstring& errorMessage = -// -// if (lastError == ERROR_FILE_EXISTS) -// throw ErrorTargetExisting(errorMessage); -// -// if (lastError == ERROR_PATH_NOT_FOUND) -// throw ErrorTargetPathMissing(errorMessage); -// -// throw FileError(errorMessage); -// } -// Loki::ScopeGuard guardTarget = Loki::MakeGuard(&removeFile, targetFile); //transactional behavior: guard just after opening target and before managing hFileOut -// -// ZEN_ON_SCOPE_EXIT(::CloseHandle, hFileOut); -// -// -//#ifndef _MSC_VER -//#warning teste perf von GetVolumeInformationByHandleW -//#endif -// DWORD fsFlags = 0; -// if (!GetVolumeInformationByHandleW(hFileOut, //__in HANDLE hFile, -// nullptr, //__out_opt LPTSTR lpVolumeNameBuffer, -// 0, //__in DWORD nVolumeNameSize, -// nullptr, //__out_opt LPDWORD lpVolumeSerialNumber, -// nullptr, //__out_opt LPDWORD lpMaximumComponentLength, -// &fsFlags, //__out_opt LPDWORD lpFileSystemFlags, -// nullptr, //__out LPTSTR lpFileSystemNameBuffer, -// 0)) //__in DWORD nFileSystemNameSize -// throw FileError(Error reading file attributes:") + "\n\"" + sourceFile + "\"" + "\n\n" + getLastErrorFormatted()); -// -// const bool sourceIsEncrypted = (infoFileIn.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) != 0; -// const bool sourceIsCompressed = (infoFileIn.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; -// const bool sourceIsSparse = (infoFileIn.dwFileAttributes & FILE_ATTRIBUTE_SPARSE_FILE) != 0; -// -// bool targetSupportSparse = (fsFlags & FILE_SUPPORTS_SPARSE_FILES) != 0; -// bool targetSupportCompressed = (fsFlags & FILE_FILE_COMPRESSION ) != 0; -// bool targetSupportStreams = (fsFlags & FILE_NAMED_STREAMS ) != 0; -// -// -// const bool useBackupFun = !sourceIsEncrypted; //http://msdn.microsoft.com/en-us/library/aa362509(v=VS.85).aspx -// -// if (sourceIsCompressed && targetSupportCompressed) -// { -// USHORT cmpState = COMPRESSION_FORMAT_DEFAULT; -// -// DWORD bytesReturned = 0; -// if (!DeviceIoControl(hFileOut, //handle to file or directory -// FSCTL_SET_COMPRESSION, //dwIoControlCode -// &cmpState, //input buffer -// sizeof(cmpState), //size of input buffer -// nullptr, //lpOutBuffer -// 0, //OutBufferSize -// &bytesReturned, //number of bytes returned -// nullptr)) //OVERLAPPED structure -// throw FileError( ddd + -// "\nFailed to write NTFS compressed attribute!"); -// } -// -// //although it seems the sparse attribute is set automatically by BackupWrite, we are required to do this manually: http://support.microsoft.com/kb/271398/en-us -// if (sourceIsSparse && targetSupportSparse) -// { -// if (useBackupFun) -// { -// DWORD bytesReturned = 0; -// if (!DeviceIoControl(hFileOut, //handle to file -// FSCTL_SET_SPARSE, //dwIoControlCode -// nullptr, //input buffer -// 0, //size of input buffer -// nullptr, //lpOutBuffer -// 0, //OutBufferSize -// &bytesReturned, //number of bytes returned -// nullptr)) //OVERLAPPED structure -// throw FileError(dddd -// "\nFailed to write NTFS sparse attribute!"); -// } -// } -// -// -// const DWORD BUFFER_SIZE = 512 * 1024; //512 kb seems to be a reasonable buffer size -// static boost::thread_specific_ptr<std::vector<char>> cpyBuf; -// if (!cpyBuf.get()) -// cpyBuf.reset(new std::vector<char>(BUFFER_SIZE)); //512 kb seems to be a reasonable buffer size -// std::vector<char>& buffer = *cpyBuf; -// -// struct ManageCtxt //manage context for BackupRead()/BackupWrite() -// { -// ManageCtxt() : read(nullptr), write(nullptr) {} -// ~ManageCtxt() -// { -// if (read != nullptr) -// ::BackupRead (0, nullptr, 0, nullptr, true, false, &read); -// if (write != nullptr) -// ::BackupWrite(0, nullptr, 0, nullptr, true, false, &write); -// } -// -// LPVOID read; -// LPVOID write; -// } context; -// -// //copy contents of sourceFile to targetFile -// UInt64 totalBytesTransferred; -// -// bool eof = false; -// do -// { -// DWORD bytesRead = 0; -// -// if (useBackupFun) -// { -// if (!::BackupRead(hFileIn, //__in HANDLE hFile, -// &buffer[0], //__out LPBYTE lpBuffer, -// BUFFER_SIZE, //__in DWORD nNumberOfBytesToRead, -// &bytesRead, //__out LPDWORD lpNumberOfBytesRead, -// false, //__in BOOL bAbort, -// false, //__in BOOL bProcessSecurity, -// &context.read)) //__out LPVOID *lpContext -// throw FileError(Error reading file:") + "\n\"" + sourceFile + "\"" + -// "\n\n" + getLastErrorFormatted()); -// } -// else if (!::ReadFile(hFileIn, //__in HANDLE hFile, -// &buffer[0], //__out LPVOID lpBuffer, -// BUFFER_SIZE, //__in DWORD nNumberOfBytesToRead, -// &bytesRead, //__out_opt LPDWORD lpNumberOfBytesRead, -// nullptr)) //__inout_opt LPOVERLAPPED lpOverlapped -// throw FileError(Error reading file:") + "\n\"" + sourceFile + "\"" + -// "\n\n" + getLastErrorFormatted()); -// -// if (bytesRead > BUFFER_SIZE) -// throw FileError(Error reading file:") + "\n\"" + sourceFile + "\"" + -// "\n\n" + "buffer overflow"); -// -// if (bytesRead < BUFFER_SIZE) -// eof = true; -// -// DWORD bytesWritten = 0; -// -// if (useBackupFun) -// { -// if (!::BackupWrite(hFileOut, //__in HANDLE hFile, -// &buffer[0], //__in LPBYTE lpBuffer, -// bytesRead, //__in DWORD nNumberOfBytesToWrite, -// &bytesWritten, //__out LPDWORD lpNumberOfBytesWritten, -// false, //__in BOOL bAbort, -// false, //__in BOOL bProcessSecurity, -// &context.write)) //__out LPVOID *lpContext -// throw FileError(ddd" (w)"); //w -> distinguish from fopen error message! -// } -// else if (!::WriteFile(hFileOut, //__in HANDLE hFile, -// &buffer[0], //__out LPVOID lpBuffer, -// bytesRead, //__in DWORD nNumberOfBytesToWrite, -// &bytesWritten, //__out_opt LPDWORD lpNumberOfBytesWritten, -// nullptr)) //__inout_opt LPOVERLAPPED lpOverlapped -// throw FileError(ddd" (w)"); //w -> distinguish from fopen error message! -// -// if (bytesWritten != bytesRead) -// throw FileError(ddd + "incomplete write"); -// -// totalBytesTransferred += bytesRead; -// -//#ifndef _MSC_VER -//#warning totalBytesTransferred kann größer als filesize sein!! -//#endif -// -// //invoke callback method to update progress indicators -// if (callback != nullptr) -// switch (callback->updateCopyStatus(totalBytesTransferred)) -// { -// case CallbackCopyFile::CONTINUE: -// break; -// -// case CallbackCopyFile::CANCEL: //a user aborted operation IS an error condition! -// throw FileError(Error copying file:") + "\n\"" + sourceFile + "\" ->\n\"" + -// targetFile + "\"\n\n" + Operation aborted!")); -// } -// } -// while (!eof); -// -// -// if (totalBytesTransferred == 0) //BackupRead silently fails reading encrypted files -> double check! -// { -// LARGE_INTEGER inputSize = {}; -// if (!::GetFileSizeEx(hFileIn, &inputSize)) -// throw FileError(Error reading file attributes:") + "\n\"" + sourceFile + "\"" + "\n\n" + getLastErrorFormatted()); -// -// if (inputSize.QuadPart != 0) -// throw FileError(Error reading file:") + "\n\"" + sourceFile + "\"" + "\n\n" + "unknown error"); -// } -// -// //time needs to be set at the end: BackupWrite() changes file time -// if (!::SetFileTime(hFileOut, -// &infoFileIn.ftCreationTime, -// nullptr, -// &infoFileIn.ftLastWriteTime)) -// throw FileError(Error changing modification time:") + "\n\"" + targetFile + "\"" + "\n\n" + getLastErrorFormatted()); -// -// -//#ifndef NDEBUG //dst hack: verify data written -// if (dst::isFatDrive(targetFile)) //throw() -// { -// WIN32_FILE_ATTRIBUTE_DATA debugeAttr = {}; -// assert(::GetFileAttributesEx(applyLongPathPrefix(targetFile).c_str(), //__in LPCTSTR lpFileName, -// GetFileExInfoStandard, //__in GET_FILEEX_INFO_LEVELS fInfoLevelId, -// &debugeAttr)); //__out LPVOID lpFileInformation -// -// assert(::CompareFileTime(&debugeAttr.ftCreationTime, &infoFileIn.ftCreationTime) == 0); -// assert(::CompareFileTime(&debugeAttr.ftLastWriteTime, &infoFileIn.ftLastWriteTime) == 0); -// } -//#endif -// -// guardTarget.Dismiss(); -// -// /* -// //create test sparse file -// HANDLE hSparse = ::CreateFile(L"C:\\sparse.file", -// GENERIC_READ | GENERIC_WRITE, //read access required for FSCTL_SET_COMPRESSION -// FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, -// 0, -// CREATE_NEW, -// FILE_FLAG_SEQUENTIAL_SCAN, -// nullptr); -// DWORD br = 0; -// if (!::DeviceIoControl(hSparse, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &br,nullptr)) -// throw 1; -// -// LARGE_INTEGER liDistanceToMove = {}; -// liDistanceToMove.QuadPart = 1024 * 1024 * 1024; //create 5 TB sparse file -// liDistanceToMove.QuadPart *= 5 * 1024; // -// if (!::SetFilePointerEx(hSparse, liDistanceToMove, nullptr, FILE_BEGIN)) -// throw 1; -// -// if (!SetEndOfFile(hSparse)) -// throw 1; -// -// FILE_ZERO_DATA_INFORMATION zeroInfo = {}; -// zeroInfo.BeyondFinalZero.QuadPart = liDistanceToMove.QuadPart; -// if (!::DeviceIoControl(hSparse, FSCTL_SET_ZERO_DATA, &zeroInfo, sizeof(zeroInfo), nullptr, 0, &br, nullptr)) -// throw 1; -// -// ::CloseHandle(hSparse); -// */ -//} -#endif - -#ifdef FFS_LINUX -void rawCopyStream(const Zstring& sourceFile, +#elif defined FFS_LINUX +void copyFileLinux(const Zstring& sourceFile, const Zstring& targetFile, CallbackCopyFile* callback, FileAttrib* newAttrib) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting @@ -2088,7 +2152,7 @@ void rawCopyStream(const Zstring& sourceFile, //invoke callback method to update progress indicators if (callback) - callback->updateCopyStatus(totalBytesTransferred); + callback->updateCopyStatus(totalBytesTransferred); //throw X! } while (!fileIn.eof()); } @@ -2102,7 +2166,7 @@ void rawCopyStream(const Zstring& sourceFile, { struct ::stat srcInfo = {}; if (::stat(sourceFile.c_str(), &srcInfo) != 0) //read file attributes from source directory - throw FileError(_("Error reading file attributes:") + L"\n\"" + sourceFile + L"\"" + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(sourceFile)) + L"\n\n" + getLastErrorFormatted()); struct ::utimbuf newTimes = {}; newTimes.actime = srcInfo.st_atime; @@ -2110,13 +2174,13 @@ void rawCopyStream(const Zstring& sourceFile, //set new "last write time" if (::utime(targetFile.c_str(), &newTimes) != 0) - throw FileError(_("Error changing modification time:") + L"\n\"" + targetFile + L"\"" + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(targetFile)) + L"\n\n" + getLastErrorFormatted()); if (newAttrib) { struct ::stat trgInfo = {}; if (::stat(targetFile.c_str(), &trgInfo) != 0) //read file attributes from source directory - throw FileError(_("Error reading file attributes:") + L"\n\"" + targetFile + L"\"" + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(targetFile)) + L"\n\n" + getLastErrorFormatted()); newAttrib->fileSize = UInt64(srcInfo.st_size); newAttrib->modificationTime = srcInfo.st_mtime; @@ -2130,31 +2194,41 @@ void rawCopyStream(const Zstring& sourceFile, #endif +Zstring createTempName(const Zstring& filename) +{ + Zstring output = filename + zen::TEMP_FILE_ENDING; + + //ensure uniqueness + for (int i = 1; somethingExists(output); ++i) + output = filename + Zchar('_') + numberTo<Zstring>(i) + zen::TEMP_FILE_ENDING; + + return output; +} + + +/* + File Copy Layers + ================ + + copyFile (setup transactional behavior) + | + copyFileSelectOs + / \ +copyFileLinux copyFileWindows (solve 8.3 issue) + | + copyFileWindowsSelectRoutine + / \ +copyFileWindowsDefault(::CopyFileEx) copyFileWindowsSparse(::BackupRead/::BackupWrite) +*/ + inline -void copyFileImpl(const Zstring& sourceFile, - const Zstring& targetFile, - CallbackCopyFile* callback, - FileAttrib* sourceAttr) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked +void copyFileSelectOs(const Zstring& sourceFile, const Zstring& targetFile, CallbackCopyFile* callback, FileAttrib* sourceAttr) { #ifdef FFS_WIN - /* - rawCopyWinApi() rawCopyWinOptimized() - ------------------------------------- - Attributes YES YES - Filetimes YES YES - ADS YES YES - Encrypted YES YES - Compressed NO YES - Sparse NO YES - PERF - 6% faster - SAMBA, ect. YES UNKNOWN! -> issues writing ADS to Samba, issues reading from NAS, error copying files having "blocked" state... ect. damn! - */ - - rawCopyWinApi(sourceFile, targetFile, callback, sourceAttr); //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked - //rawCopyWinOptimized(sourceFile, targetFile, callback); //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked ->about 8% faster + copyFileWindows(sourceFile, targetFile, callback, sourceAttr); //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked #elif defined FFS_LINUX - rawCopyStream(sourceFile, targetFile, callback, sourceAttr); //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting + copyFileLinux(sourceFile, targetFile, callback, sourceAttr); //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting #endif } } @@ -2170,12 +2244,12 @@ void zen::copyFile(const Zstring& sourceFile, //throw FileError, ErrorTargetPath if (transactionalCopy) { Zstring temporary = targetFile + zen::TEMP_FILE_ENDING; //use temporary file until a correct date has been set - zen::ScopeGuard guardTempFile = zen::makeGuard([&]() { removeFile(temporary); }); //transactional behavior: ensure cleanup (e.g. network drop) -> ref to temporary[!] + zen::ScopeGuard guardTempFile = zen::makeGuard([&] { try { removeFile(temporary); } catch (...) {} }); //transactional behavior: ensure cleanup (e.g. network drop) -> ref to temporary[!] //raw file copy try { - copyFileImpl(sourceFile, temporary, callback, sourceAttr); //throw FileError: ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked + copyFileSelectOs(sourceFile, temporary, callback, sourceAttr); //throw FileError: ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked } catch (ErrorTargetExisting&) { @@ -2184,7 +2258,7 @@ void zen::copyFile(const Zstring& sourceFile, //throw FileError, ErrorTargetPath temporary = createTempName(targetFile); //retry - copyFileImpl(sourceFile, temporary, callback, sourceAttr); //throw FileError + copyFileSelectOs(sourceFile, temporary, callback, sourceAttr); //throw FileError } //have target file deleted (after read access on source and target has been confirmed) => allow for almost transactional overwrite @@ -2202,7 +2276,7 @@ void zen::copyFile(const Zstring& sourceFile, //throw FileError, ErrorTargetPath //have target file deleted if (callback) callback->deleteTargetFile(targetFile); - copyFileImpl(sourceFile, targetFile, callback, sourceAttr); //throw FileError: ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked + copyFileSelectOs(sourceFile, targetFile, callback, sourceAttr); //throw FileError: ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked } /* Note: non-transactional file copy solves at least four problems: @@ -2215,7 +2289,7 @@ void zen::copyFile(const Zstring& sourceFile, //throw FileError, ErrorTargetPath //file permissions if (copyFilePermissions) { - zen::ScopeGuard guardTargetFile = zen::makeGuard([&] { removeFile(targetFile);}); + zen::ScopeGuard guardTargetFile = zen::makeGuard([&] { try { removeFile(targetFile); } catch (...) {}}); copyObjectPermissions(sourceFile, targetFile, SYMLINK_FOLLOW); //throw FileError diff --git a/zen/file_handling.h b/zen/file_handling.h index b0d97a6c..e90ad544 100644 --- a/zen/file_handling.h +++ b/zen/file_handling.h @@ -45,6 +45,7 @@ void setFileTime(const Zstring& filename, const Int64& modificationTime, ProcSym //symlink handling: always evaluate target UInt64 getFilesize(const Zstring& filename); //throw FileError +UInt64 getFreeDiskSpace(const Zstring& path); //throw FileError //file handling bool removeFile(const Zstring& filename); //return "true" if file was actually deleted; throw FileError @@ -54,13 +55,10 @@ void removeDirectory(const Zstring& directory, CallbackRemoveDir* callback = nul //rename file or directory: no copying!!! void renameFile(const Zstring& oldName, const Zstring& newName); //throw FileError; -//move source to target; expectations: all super-directories of target exist -//"ignoreExisting": if target already exists, source is deleted -void moveFile(const Zstring& sourceFile, const Zstring& targetFile, bool ignoreExisting, CallbackMoveFile* callback); //throw FileError; - -//move source to target including subdirectories -//"ignoreExisting": existing directories and files will be enriched -void moveDirectory(const Zstring& sourceDir, const Zstring& targetDir, bool ignoreExisting, CallbackMoveFile* callback); //throw FileError; +//move source to target across volumes; prerequisite: all super-directories of target exist +//if target already contains some files/dirs they are seen as remnants of a previous incomplete move - see comment in moveDirectoryImpl +void moveFile(const Zstring& sourceFile, const Zstring& targetFile, CallbackMoveFile* callback); //throw FileError +void moveDirectory(const Zstring& sourceDir, const Zstring& targetDir, CallbackMoveFile* callback); //throw FileError bool supportsPermissions(const Zstring& dirname); //throw FileError, derefernces symlinks @@ -109,7 +107,7 @@ struct CallbackCopyFile //callback functionality //may throw: //Linux: unconditionally - //Windows: first exception is swallowed, requestUiRefresh() is then called again where it should throw again and exception will propagate as expected + //Windows: first exception is swallowed, updateCopyStatus() is then called again where it should throw again and exception will propagate as expected virtual void updateCopyStatus(UInt64 totalBytesTransferred) = 0; }; diff --git a/zen/file_io.cpp b/zen/file_io.cpp index 4c38bb22..462364da 100644 --- a/zen/file_io.cpp +++ b/zen/file_io.cpp @@ -27,7 +27,7 @@ FileInput::FileInput(const Zstring& filename) : //throw FileError, ErrorNotExis fileHandle = ::CreateFile(zen::applyLongPathPrefix(filename).c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //all shared modes are required to read open files that are shared by other applications - 0, + nullptr, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, /* possible values: (Reference http://msdn.microsoft.com/en-us/library/aa363858(VS.85).aspx#caching_behavior) @@ -62,7 +62,7 @@ FileInput::FileInput(const Zstring& filename) : //throw FileError, ErrorNotExis #endif { const ErrorCode lastError = getLastError(); - std::wstring errorMessage = _("Error reading file:") + L"\n\"" + filename_ + L"\"" + L"\n\n" + zen::getLastErrorFormatted(lastError) + L" (open)"; + std::wstring errorMessage = replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(filename_)) + L"\n\n" + getLastErrorFormatted(lastError) + L" (open)"; if (errorCodeForNotExisting(lastError)) throw ErrorNotExisting(errorMessage); @@ -95,7 +95,7 @@ size_t FileInput::read(void* buffer, size_t bytesToRead) //returns actual number const size_t bytesRead = ::fread(buffer, 1, bytesToRead, fileHandle); if (::ferror(fileHandle) != 0) #endif - throw FileError(_("Error reading file:") + L"\n\"" + filename_ + L"\"" + L"\n\n" + zen::getLastErrorFormatted() + L" (read)"); + throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(filename_)) + L"\n\n" + getLastErrorFormatted() + L" (read)"); #ifdef FFS_WIN if (bytesRead < bytesToRead) //verify only! @@ -105,7 +105,7 @@ size_t FileInput::read(void* buffer, size_t bytesToRead) //returns actual number eofReached = true; if (bytesRead > bytesToRead) - throw FileError(_("Error reading file:") + L"\n\"" + filename_ + L"\"" + L"\n\n" + L"buffer overflow"); + throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(filename_)) + L"\n\n" + L"buffer overflow"); return bytesRead; } @@ -132,16 +132,17 @@ FileOutput::FileOutput(const Zstring& filename, AccessFlag access) : //throw Fil The resulting code is faster, because the redirector can use the cache manager and send fewer SMBs with more data. This combination also avoids an issue where writing to a file across a network can occasionally return ERROR_ACCESS_DENIED. */ FILE_SHARE_READ | FILE_SHARE_DELETE, //note: FILE_SHARE_DELETE is required to rename file while handle is open! - 0, + nullptr, access == ACC_OVERWRITE ? CREATE_ALWAYS : CREATE_NEW, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, nullptr); if (fileHandle == INVALID_HANDLE_VALUE) { const DWORD lastError = ::GetLastError(); - std::wstring errorMessage = _("Error writing file:") + L"\n\"" + filename_ + L"\"" L"\n\n" + zen::getLastErrorFormatted(lastError); + const std::wstring errorMessage = replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(filename_)) + L"\n\n" + zen::getLastErrorFormatted(lastError); - if (lastError == ERROR_FILE_EXISTS) + if (lastError == ERROR_FILE_EXISTS || //confirmed to be used + lastError == ERROR_ALREADY_EXISTS) //comment on msdn claims, this one is used on Windows Mobile 6 throw ErrorTargetExisting(errorMessage); if (lastError == ERROR_PATH_NOT_FOUND) @@ -157,7 +158,7 @@ FileOutput::FileOutput(const Zstring& filename, AccessFlag access) : //throw Fil if (!fileHandle) { const int lastError = errno; - std::wstring errorMessage = _("Error writing file:") + L"\n\"" + filename_ + L"\"" L"\n\n" + zen::getLastErrorFormatted(lastError); + const std::wstring errorMessage = replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(filename_)) + L"\n\n" + zen::getLastErrorFormatted(lastError); if (lastError == EEXIST) throw ErrorTargetExisting(errorMessage); @@ -193,8 +194,8 @@ void FileOutput::write(const void* buffer, size_t bytesToWrite) //throw FileErro const size_t bytesWritten = ::fwrite(buffer, 1, bytesToWrite, fileHandle); if (::ferror(fileHandle) != 0) #endif - throw FileError(_("Error writing file:") + L"\n\"" + filename_ + L"\"" L"\n\n" + zen::getLastErrorFormatted() + L" (w)"); //w -> distinguish from fopen error message! + throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(filename_)) + L"\n\n" + getLastErrorFormatted() + L" (w)"); //w -> distinguish from fopen error message! if (bytesWritten != bytesToWrite) //must be fulfilled for synchronous writes! - throw FileError(_("Error writing file:") + L"\n\"" + filename_ + L"\"" L"\n\n" + L"incomplete write"); + throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(filename_)) + L"\n\n" + L"(incomplete write)"); } diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp index c3536825..ea6aa289 100644 --- a/zen/file_traverser.cpp +++ b/zen/file_traverser.cpp @@ -63,7 +63,7 @@ bool extractFileInfoFromSymlink(const Zstring& linkName, zen::TraverseCallback:: HANDLE hFile = ::CreateFile(zen::applyLongPathPrefix(linkName).c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - 0, + nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); @@ -76,7 +76,7 @@ bool extractFileInfoFromSymlink(const Zstring& linkName, zen::TraverseCallback:: return false; //write output - output.fileSize = zen::UInt64(fileInfoByHandle.nFileSizeLow, fileInfoByHandle.nFileSizeHigh); + output.fileSize = UInt64(fileInfoByHandle.nFileSizeLow, fileInfoByHandle.nFileSizeHigh); output.lastWriteTimeRaw = toTimeT(fileInfoByHandle.ftLastWriteTime); output.id = FileId(); //= extractFileID(fileInfoByHandle); -> id from dereferenced symlink is problematic, since renaming will consider the link, not the target! return true; @@ -91,16 +91,6 @@ DWORD retrieveVolumeSerial(const Zstring& pathName) //returns 0 on error or if s //- indirection: subst S: %USERPROFILE% // -> GetVolumePathName() on the other hand resolves "S:\Desktop\somedir" to "S:\Desktop\" - nice try... - typedef BOOL (WINAPI* GetFileInformationByHandleFunc)(HANDLE hFile, - LPBY_HANDLE_FILE_INFORMATION lpFileInformation); - - const SysDllFun<GetFileInformationByHandleFunc> getFileInformationByHandle(L"kernel32.dll", "GetFileInformationByHandle"); //available since Windows XP - if (!getFileInformationByHandle) - { - assert(false); - return 0; - } - const HANDLE hDir = ::CreateFile(zen::applyLongPathPrefix(pathName).c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, @@ -113,8 +103,7 @@ DWORD retrieveVolumeSerial(const Zstring& pathName) //returns 0 on error or if s ZEN_ON_SCOPE_EXIT(::CloseHandle(hDir)); BY_HANDLE_FILE_INFORMATION fileInfo = {}; - if (!getFileInformationByHandle(hDir, //__in HANDLE hFile, - &fileInfo)) //__out LPBY_HANDLE_FILE_INFORMATION lpFileInformation + if (!::GetFileInformationByHandle(hDir, &fileInfo)) return 0; return fileInfo.dwVolumeSerialNumber; @@ -135,18 +124,16 @@ DWORD retrieveVolumeSerial(const Zstring& pathName) //returns 0 on error! BUFFER_SIZE)) //__in DWORD cchBufferLength return 0; - Zstring volumePath = &buffer[0]; - if (!endsWith(volumePath, FILE_NAME_SEPARATOR)) - volumePath += FILE_NAME_SEPARATOR; + Zstring volumePath = appendSeparator(&buffer[0]); DWORD volumeSerial = 0; if (!::GetVolumeInformation(volumePath.c_str(), //__in_opt LPCTSTR lpRootPathName, - nullptr, //__out LPTSTR lpVolumeNameBuffer, + nullptr, //__out LPTSTR lpVolumeNameBuffer, 0, //__in DWORD nVolumeNameSize, &volumeSerial, //__out_opt LPDWORD lpVolumeSerialNumber, - nullptr, //__out_opt LPDWORD lpMaximumComponentLength, - nullptr, //__out_opt LPDWORD lpFileSystemFlags, - nullptr, //__out LPTSTR lpFileSystemNameBuffer, + nullptr, //__out_opt LPDWORD lpMaximumComponentLength, + nullptr, //__out_opt LPDWORD lpFileSystemFlags, + nullptr, //__out LPTSTR lpFileSystemNameBuffer, 0)) //__in DWORD nFileSystemNameSize return 0; @@ -157,9 +144,9 @@ DWORD retrieveVolumeSerial(const Zstring& pathName) //returns 0 on error! const bool isXpOrLater = winXpOrLater(); //VS2010 compiled DLLs are not supported on Win 2000: Popup dialog "DecodePointer not found" -const DllFun<findplus::OpenDirFunc> openDir = isXpOrLater ? DllFun<findplus::OpenDirFunc >(findplus::getDllName(), findplus::openDirFuncName ) : DllFun<findplus::OpenDirFunc >(); // -const DllFun<findplus::ReadDirFunc> readDir = isXpOrLater ? DllFun<findplus::ReadDirFunc >(findplus::getDllName(), findplus::readDirFuncName ) : DllFun<findplus::ReadDirFunc >(); //load at startup: avoid pre C++11 static initialization MT issues -const DllFun<findplus::CloseDirFunc> closeDir= isXpOrLater ? DllFun<findplus::CloseDirFunc>(findplus::getDllName(), findplus::closeDirFuncName) : DllFun<findplus::CloseDirFunc>(); // +const auto openDir = isXpOrLater ? DllFun<findplus::FunType_openDir >(findplus::getDllName(), findplus::funName_openDir ) : DllFun<findplus::FunType_openDir >(); // +const auto readDir = isXpOrLater ? DllFun<findplus::FunType_readDir >(findplus::getDllName(), findplus::funName_readDir ) : DllFun<findplus::FunType_readDir >(); //load at startup: avoid pre C++11 static initialization MT issues +const auto closeDir= isXpOrLater ? DllFun<findplus::FunType_closeDir>(findplus::getDllName(), findplus::funName_closeDir) : DllFun<findplus::FunType_closeDir>(); // /* Common C-style interface for Win32 FindFirstFile(), FindNextFile() and FileFilePlus openDir(), closeDir(): @@ -203,21 +190,17 @@ struct Win32Traverser static void create(const Zstring& directory, DirHandle& hnd) //throw FileError { - const Zstring& directoryPf = endsWith(directory, FILE_NAME_SEPARATOR) ? - directory : - directory + FILE_NAME_SEPARATOR; + const Zstring& directoryPf = appendSeparator(directory); hnd.searchHandle = ::FindFirstFile(applyLongPathPrefix(directoryPf + L'*').c_str(), &hnd.firstData); //no noticable performance difference compared to FindFirstFileEx with FindExInfoBasic, FIND_FIRST_EX_CASE_SENSITIVE and/or FIND_FIRST_EX_LARGE_FETCH if (hnd.searchHandle == INVALID_HANDLE_VALUE) - throw FileError(_("Error traversing directory:") + L"\n\"" + directory + L"\"" + L"\n\n" + zen::getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + getLastErrorFormatted()); //::GetLastError() == ERROR_FILE_NOT_FOUND -> *usually* NOT okay: - //however it is unclear whether this indicates a missing directory or a completely empty directory - // note: not all directories contain "., .." entries! E.g. a drive's root directory or NetDrive + ftp.gnu.org\CRYPTO.README" - // -> addon: this is NOT a directory, it looks like one in NetDrive, but it's a file in Opera! - //we have to guess it's former and let the error propagate - // -> FindFirstFile() is a nice example of violation of API design principle of single responsibility and its consequences + //directory may not exist *or* it is completely empty: not all directories contain "., .." entries, e.g. a drive's root directory + //usually a directory is never completely empty due to "sync.ffs_lock", so we assume it's not existing and let the error propagate + // -> FindFirstFile() is a nice example of violation of API design principle of single responsibility } static void destroy(const DirHandle& hnd) { ::FindClose(hnd.searchHandle); } //throw() @@ -237,7 +220,7 @@ struct Win32Traverser if (::GetLastError() == ERROR_NO_MORE_FILES) //not an error situation return false; //else we have a problem... report it: - throw FileError(_("Error traversing directory:") + L"\n\"" + directory + L"\"" + L"\n\n" + zen::getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + getLastErrorFormatted()); } return true; } @@ -285,7 +268,7 @@ struct FilePlusTraverser { hnd.searchHandle = ::openDir(applyLongPathPrefix(directory).c_str()); if (hnd.searchHandle == nullptr) - throw FileError(_("Error traversing directory:") + L"\n\"" + directory + L"\"" + L"\n\n" + zen::getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + getLastErrorFormatted()); } static void destroy(DirHandle hnd) { ::closeDir(hnd.searchHandle); } //throw() @@ -304,7 +287,7 @@ struct FilePlusTraverser this is required for NetDrive mounted Webdav, e.g. www.box.net and NT4, 2000 remote drives, et al. NT status code | Win32 error code - ----------------------------------------------------------- + --------------------------------|-------------------------- STATUS_INVALID_LEVEL | ERROR_INVALID_LEVEL STATUS_NOT_SUPPORTED | ERROR_NOT_SUPPORTED STATUS_INVALID_PARAMETER | ERROR_INVALID_PARAMETER @@ -327,7 +310,7 @@ struct FilePlusTraverser } //else we have a problem... report it: - throw FileError(_("Error traversing directory:") + L"\n\"" + directory + L"\"" + L"\n\n" + zen::getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + getLastErrorFormatted()); } return true; } @@ -394,7 +377,7 @@ private: tryReportingError([&] { if (level == 100) //notify endless recursion - throw FileError(_("Endless loop when traversing directory:") + L"\n\"" + directory + L"\""); + throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + _("Endless loop.")); }, sink); typename Trav::DirHandle searchHandle; @@ -433,9 +416,7 @@ private: (shortName[1] == 0 || (shortName[1] == L'.' && shortName[2] == 0))) continue; - const Zstring& fullName = endsWith(directory, FILE_NAME_SEPARATOR) ? - directory + shortName : - directory + FILE_NAME_SEPARATOR + shortName; + const Zstring& fullName = appendSeparator(directory) + shortName; if (Trav::isSymlink(fileInfo) && !followSymlinks_) //evaluate symlink directly { @@ -477,7 +458,7 @@ private: { const dst::RawTime rawTime(Trav::getCreateTimeRaw(fileInfo), Trav::getModTimeRaw(fileInfo)); - if (dst::fatHasUtcEncoded(rawTime)) //throw (std::runtime_error) + if (dst::fatHasUtcEncoded(rawTime)) //throw std::runtime_error details.lastWriteTimeRaw = toTimeT(dst::fatDecodeUtcTime(rawTime)); //return real UTC time; throw (std::runtime_error) else markForDstHack.push_back(std::make_pair(fullName, Trav::getModTimeRaw(fileInfo))); @@ -504,7 +485,7 @@ private: dstCallback.requestUiRefresh(i->first); - const dst::RawTime encodedTime = dst::fatEncodeUtcTime(i->second); //throw (std::runtime_error) + const dst::RawTime encodedTime = dst::fatEncodeUtcTime(i->second); //throw std::runtime_error { //may need to remove the readonly-attribute (e.g. FAT usb drives) FileUpdateHandle updateHandle(i->first, [=] @@ -512,7 +493,7 @@ private: return ::CreateFile(zen::applyLongPathPrefix(i->first).c_str(), GENERIC_READ | GENERIC_WRITE, //use both when writing over network, see comment in file_io.cpp FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - 0, + nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); @@ -599,7 +580,7 @@ private: tryReportingError([&] { if (level == 100) //notify endless recursion - throw FileError(_("Endless loop when traversing directory:") + L"\n\"" + directory + L"\""); + throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + _("Endless loop.")); }, sink); @@ -608,7 +589,7 @@ private: { dirObj = ::opendir(directory.c_str()); //directory must NOT end with path separator, except "/" if (!dirObj) - throw FileError(_("Error traversing directory:") + L"\n\"" + directory + L"\"" + L"\n\n" + zen::getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + getLastErrorFormatted()); }, sink); if (!dirObj) @@ -621,7 +602,7 @@ private: tryReportingError([&] { if (::readdir_r(dirObj, reinterpret_cast< ::dirent*>(&buffer[0]), &dirEntry) != 0) - throw FileError(_("Error traversing directory:") + L"\n\"" + directory + L"\"" + L"\n\n" + zen::getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + getLastErrorFormatted()); }, sink); if (!dirEntry) //no more items or ignore error return; @@ -633,16 +614,14 @@ private: (shortName[1] == 0 || (shortName[1] == '.' && shortName[2] == 0))) continue; - const Zstring& fullName = endsWith(directory, FILE_NAME_SEPARATOR) ? //e.g. "/" - directory + shortName : - directory + FILE_NAME_SEPARATOR + shortName; + const Zstring& fullName = appendSeparator(directory) + shortName; struct ::stat fileInfo = {}; bool haveData = false; tryReportingError([&] { if (::lstat(fullName.c_str(), &fileInfo) != 0) //lstat() does not resolve symlinks - throw FileError(_("Error reading file attributes:") + L"\n\"" + fullName + L"\"" + L"\n\n" + zen::getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(fullName)) + L"\n\n" + getLastErrorFormatted()); haveData = true; }, sink); if (!haveData) diff --git a/zen/file_update_handle.h b/zen/file_update_handle.h index aa9edebd..716048fd 100644 --- a/zen/file_update_handle.h +++ b/zen/file_update_handle.h @@ -24,7 +24,7 @@ public: const DWORD lastError = ::GetLastError(); if (lastError == ERROR_ACCESS_DENIED) //function fails if file is read-only { - zen::ScopeGuard guardErrorCode = zen::makeGuard([&]() { ::SetLastError(lastError); }); //transactional behavior: ensure cleanup (e.g. network drop) -> cref [!] + //zen::ScopeGuard guardErrorCode = zen::makeGuard([&] { ::SetLastError(lastError); }); //transactional behavior: ensure cleanup (e.g. network drop) -> cref [!] //read-only file attribute may cause trouble: temporarily reset it const DWORD tmpAttr = ::GetFileAttributes(filenameFmt.c_str()); @@ -32,7 +32,7 @@ public: { if (::SetFileAttributes(filenameFmt.c_str(), FILE_ATTRIBUTE_NORMAL)) { - guardErrorCode.dismiss(); + //guardErrorCode.dismiss(); attr = tmpAttr; //"create" guard on read-only attribute //now try again @@ -28,7 +28,8 @@ namespace zen inline std::string generateGUID() //creates a 16 byte GUID { - boost::uuids::uuid nativeRep = boost::uuids::random_generator()(); + boost::uuids::uuid nativeRep = boost::uuids::random_generator()(); //generator is thread-safe like an int + //perf: generator: 0.22ms per call; retrieve GUID: 0.12µs per call return std::string(nativeRep.begin(), nativeRep.end()); } } diff --git a/zen/long_path_prefix.h b/zen/long_path_prefix.h index 83643906..db3207d3 100644 --- a/zen/long_path_prefix.h +++ b/zen/long_path_prefix.h @@ -12,7 +12,7 @@ namespace zen { -//handle filenames longer-equal 260 (== MAX_PATH) characters by applying \\?\-prefix (Reference: http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx#maxpath) +//handle filenames longer-equal 260 (== MAX_PATH) characters by applying \\?\-prefix; see: http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx#maxpath /* 1. path must be absolute 2. if path is smaller than MAX_PATH nothing is changed! diff --git a/zen/notify_removal.cpp b/zen/notify_removal.cpp index d8657ea2..35ffc4e1 100644 --- a/zen/notify_removal.cpp +++ b/zen/notify_removal.cpp @@ -12,19 +12,6 @@ using namespace zen; -/* -//convert bitmask into "real" drive-letter -Zstring getDriveFromMask(ULONG unitmask) -{ - for (int i = 0; i < 26; ++i) - { - if (unitmask & 0x1) - return Zstring() + static_cast<DefaultChar>(DefaultChar('A') + i) + DefaultStr(":\\"); - unitmask >>= 1; - } - return Zstring(); -} -*/ namespace { @@ -64,7 +51,7 @@ private: friend LRESULT CALLBACK topWndProc(HWND, UINT, WPARAM, LPARAM); void processMessage(UINT message, WPARAM wParam, LPARAM lParam); - const HINSTANCE process; + const HINSTANCE hMainModule; HWND windowHandle; std::set<Listener*> listener; @@ -74,39 +61,39 @@ private: const wchar_t MessageProvider::WINDOW_NAME[] = L"E6AD5EB1-527B-4EEF-AC75-27883B233380"; //random name -LRESULT CALLBACK topWndProc(HWND hwnd, //handle to window - UINT uMsg, //message identifier - WPARAM wParam, //first message parameter - LPARAM lParam) //second message parameter +LRESULT CALLBACK topWndProc(HWND hwnd, //handle to window + UINT uMsg, //message identifier + WPARAM wParam, //first message parameter + LPARAM lParam) //second message parameter { if (messageProviderConstructed) //attention: this callback is triggered in the middle of singleton construction! It is a bad idea to to call back at this time! try { MessageProvider::instance().processMessage(uMsg, wParam, lParam); //not supposed to throw } - catch (...) {} + catch (...) { assert(false); } return ::DefWindowProc(hwnd, uMsg, wParam, lParam); } MessageProvider::MessageProvider() : - process(::GetModuleHandle(nullptr)), //get program's module handle + hMainModule(::GetModuleHandle(nullptr)), //get program's module handle windowHandle(nullptr) { - if (!process) + if (!hMainModule) throw zen::FileError(std::wstring(L"Could not start monitoring window notifications:") + L"\n\n" + getLastErrorFormatted() + L" (GetModuleHandle)"); //register the main window class WNDCLASS wc = {}; wc.lpfnWndProc = topWndProc; - wc.hInstance = process; + wc.hInstance = hMainModule; wc.lpszClassName = WINDOW_NAME; if (::RegisterClass(&wc) == 0) throw zen::FileError(std::wstring(L"Could not start monitoring window notifications:") + L"\n\n" + getLastErrorFormatted() + L" (RegisterClass)"); - zen::ScopeGuard guardClass = zen::makeGuard([&]() { ::UnregisterClass(WINDOW_NAME, process); }); + ScopeGuard guardClass = makeGuard([&] { ::UnregisterClass(WINDOW_NAME, hMainModule); }); //create dummy-window windowHandle = ::CreateWindow(WINDOW_NAME, //LPCTSTR lpClassName OR ATOM in low-order word! @@ -117,9 +104,9 @@ MessageProvider::MessageProvider() : 0, //int nWidth, 0, //int nHeight, 0, //note: we need a toplevel window to receive device arrival events, not a message-window (HWND_MESSAGE)! - nullptr, //HMENU hMenu, - process, //HINSTANCE hInstance, - nullptr); //LPVOID lpParam + nullptr, //HMENU hMenu, + hMainModule, //HINSTANCE hInstance, + nullptr); //LPVOID lpParam if (!windowHandle) throw zen::FileError(std::wstring(L"Could not start monitoring window notifications:") + L"\n\n" + getLastErrorFormatted() + L" (CreateWindow)"); @@ -132,7 +119,7 @@ MessageProvider::~MessageProvider() //clean-up in reverse order ::DestroyWindow(windowHandle); ::UnregisterClass(WINDOW_NAME, //LPCTSTR lpClassName OR ATOM in low-order word! - process); //HINSTANCE hInstance + hMainModule); //HINSTANCE hInstance } @@ -147,10 +134,10 @@ void MessageProvider::processMessage(UINT message, WPARAM wParam, LPARAM lParam) class NotifyRequestDeviceRemoval::Pimpl : private MessageProvider::Listener { public: - Pimpl(NotifyRequestDeviceRemoval& parent, HANDLE hDir) : //throw (FileError) + Pimpl(NotifyRequestDeviceRemoval& parent, HANDLE hDir) : //throw FileError parent_(parent) { - MessageProvider::instance().registerListener(*this); //throw (FileError) + MessageProvider::instance().registerListener(*this); //throw FileError //register handles to receive notifications DEV_BROADCAST_HANDLE filter = {}; @@ -8,116 +8,66 @@ #define DEBUG_PERF_HEADER #include "deprecate.h" +#include "tick_count.h" #ifdef FFS_WIN #include <sstream> -#include "win.h" //includes "windows.h" #else #include <iostream> -#include <time.h> #endif -//two macros for quick performance measurements -#define PERF_START CpuTimer perfTest; +//############# two macros for quick performance measurements ############### +#define PERF_START zen::PerfTimer perfTest; #define PERF_STOP perfTest.showResult(); +//########################################################################### -#ifdef FFS_WIN -class CpuTimer +namespace zen +{ +class PerfTimer { public: class TimerError {}; ZEN_DEPRECATE - CpuTimer() : frequency(), startTime(), resultShown(false) + PerfTimer() : ticksPerSec_(ticksPerSec()), startTime(), resultShown(false) { - SetThreadAffinity dummy; - if (!::QueryPerformanceFrequency(&frequency)) - throw TimerError(); - if (!::QueryPerformanceCounter (&startTime)) + //std::clock() - "counts CPU time in C and wall time in VC++" - WTF!??? +#ifdef FFS_WIN + if (::SetThreadAffinityMask(::GetCurrentThread(), 1) == 0) throw TimerError(); //"should not be required unless there are bugs in BIOS or HAL" - msdn, QueryPerformanceCounter +#endif + startTime = getTicks(); + if (ticksPerSec_ == 0 || !startTime.isValid()) throw TimerError(); } - ~CpuTimer() - { - if (!resultShown) - showResult(); - } + ~PerfTimer() { if (!resultShown) showResult(); } void showResult() { - SetThreadAffinity dummy; - LARGE_INTEGER currentTime = {}; - if (!::QueryPerformanceCounter(¤tTime)) + const TickVal now = getTicks(); + if (!now.isValid()) throw TimerError(); - const auto delta = static_cast<long>(1000.0 * (currentTime.QuadPart - startTime.QuadPart) / frequency.QuadPart); + const auto delta = static_cast<long>(1000.0 * (now - startTime) / ticksPerSec_); +#ifdef FFS_WIN std::ostringstream ss; ss << delta << " ms"; - ::MessageBoxA(nullptr, ss.str().c_str(), "Timer", 0); - resultShown = true; - - if (!::QueryPerformanceCounter(&startTime)) - throw TimerError(); //don't include call to MessageBox()! - } - -private: - class SetThreadAffinity - { - public: - SetThreadAffinity() : oldmask(::SetThreadAffinityMask(::GetCurrentThread(), 1)) { if (oldmask == 0) throw TimerError(); } - ~SetThreadAffinity() { ::SetThreadAffinityMask(::GetCurrentThread(), oldmask); } - private: - SetThreadAffinity(const SetThreadAffinity&); - SetThreadAffinity& operator=(const SetThreadAffinity&); - const DWORD_PTR oldmask; - }; - - LARGE_INTEGER frequency; - LARGE_INTEGER startTime; - bool resultShown; -}; - - #else -class CpuTimer -{ -public: - class TimerError {}; - - ZEN_DEPRECATE - CpuTimer() : startTime(), resultShown(false) - { - //clock() seems to give grossly incorrect results: multi core issue? - //gettimeofday() seems fine but is deprecated - if (::clock_gettime(CLOCK_MONOTONIC_RAW, &startTime) != 0) //CLOCK_MONOTONIC measures time reliably across processors! - throw TimerError(); - } - - ~CpuTimer() - { - if (!resultShown) - showResult(); - } - - void showResult() - { - timespec currentTime = {}; - if (::clock_gettime(CLOCK_MONOTONIC_RAW, ¤tTime) != 0) - throw TimerError(); - - const auto delta = static_cast<long>((currentTime.tv_sec - startTime.tv_sec) * 1000.0 + (currentTime.tv_nsec - startTime.tv_nsec) / 1000000.0); std::clog << "Perf: duration: " << delta << " ms\n"; +#endif resultShown = true; - if (::clock_gettime(CLOCK_MONOTONIC_RAW, &startTime) != 0) + startTime = getTicks(); //don't include call to MessageBox()! + if (!startTime.isValid()) throw TimerError(); } private: - timespec startTime; + const std::int64_t ticksPerSec_; + TickVal startTime; bool resultShown; }; -#endif +} #endif //DEBUG_PERF_HEADER diff --git a/zen/privilege.cpp b/zen/privilege.cpp index 23a55bd8..69c820f3 100644 --- a/zen/privilege.cpp +++ b/zen/privilege.cpp @@ -15,14 +15,14 @@ bool privilegeIsActive(LPCTSTR privilege) //throw FileError if (!::OpenProcessToken(::GetCurrentProcess(), //__in HANDLE ProcessHandle, TOKEN_QUERY, //__in DWORD DesiredAccess, &hToken)) //__out PHANDLE TokenHandle - throw FileError(_("Error setting privilege:") + L" \"" + privilege + L"\"" + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot set privilege %x."), L"%x", std::wstring(L"\"") + privilege + L"\"") + L"\n\n" + getLastErrorFormatted()); ZEN_ON_SCOPE_EXIT(::CloseHandle(hToken)); LUID luid = {}; if (!::LookupPrivilegeValue(nullptr, //__in_opt LPCTSTR lpSystemName, privilege, //__in LPCTSTR lpName, &luid )) //__out PLUID lpLuid - throw FileError(_("Error setting privilege:") + L" \"" + privilege + L"\"" + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot set privilege %x."), L"%x", std::wstring(L"\"") + privilege + L"\"") + L"\n\n" + getLastErrorFormatted()); PRIVILEGE_SET priv = {}; priv.PrivilegeCount = 1; @@ -34,7 +34,7 @@ bool privilegeIsActive(LPCTSTR privilege) //throw FileError if (!::PrivilegeCheck(hToken, //__in HANDLE ClientToken, &priv, //__inout PPRIVILEGE_SET RequiredPrivileges, &alreadyGranted)) //__out LPBOOL pfResult - throw FileError(_("Error setting privilege:") + L" \"" + privilege + L"\"" + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot set privilege %x."), L"%x", std::wstring(L"\"") + privilege + L"\"") + L"\n\n" + getLastErrorFormatted()); return alreadyGranted == TRUE; } @@ -46,14 +46,14 @@ void setPrivilege(LPCTSTR privilege, bool enable) //throw FileError if (!::OpenProcessToken(::GetCurrentProcess(), //__in HANDLE ProcessHandle, TOKEN_ADJUST_PRIVILEGES, //__in DWORD DesiredAccess, &hToken)) //__out PHANDLE TokenHandle - throw FileError(_("Error setting privilege:") + L" \"" + privilege + L"\"" + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot set privilege %x."), L"%x", std::wstring(L"\"") + privilege + L"\"") + L"\n\n" + getLastErrorFormatted()); ZEN_ON_SCOPE_EXIT(::CloseHandle(hToken)); LUID luid = {}; if (!::LookupPrivilegeValue(nullptr, //__in_opt LPCTSTR lpSystemName, privilege, //__in LPCTSTR lpName, &luid )) //__out PLUID lpLuid - throw FileError(_("Error setting privilege:") + L" \"" + privilege + L"\"" + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot set privilege %x."), L"%x", std::wstring(L"\"") + privilege + L"\"") + L"\n\n" + getLastErrorFormatted()); TOKEN_PRIVILEGES tp = {}; tp.PrivilegeCount = 1; @@ -66,10 +66,10 @@ void setPrivilege(LPCTSTR privilege, bool enable) //throw FileError 0, //__in DWORD BufferLength, nullptr, //__out_opt PTOKEN_PRIVILEGES PreviousState, nullptr)) //__out_opt PDWORD ReturnLength - throw FileError(_("Error setting privilege:") + L" \"" + privilege + L"\"" + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot set privilege %x."), L"%x", std::wstring(L"\"") + privilege + L"\"") + L"\n\n" + getLastErrorFormatted()); if (::GetLastError() == ERROR_NOT_ALL_ASSIGNED) //check although previous function returned with success! - throw FileError(_("Error setting privilege:") + L" \"" + privilege + L"\"" + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot set privilege %x."), L"%x", std::wstring(L"\"") + privilege + L"\"") + L"\n\n" + getLastErrorFormatted()); } diff --git a/zen/process_status.h b/zen/process_priority.h index 15266b28..15266b28 100644 --- a/zen/process_status.h +++ b/zen/process_priority.h diff --git a/zen/recycler.cpp b/zen/recycler.cpp new file mode 100644 index 00000000..e53b5f6a --- /dev/null +++ b/zen/recycler.cpp @@ -0,0 +1,280 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#include "recycler.h" +#include <stdexcept> +#include <iterator> +#include <zen/file_handling.h> + +#ifdef FFS_WIN +#include <algorithm> +#include <functional> +#include <vector> +#include <zen/dll.h> +#include <zen/win.h> //includes "windows.h" +#include <zen/assert_static.h> +#include <zen/win_ver.h> +#include <zen/long_path_prefix.h> +#include "IFileOperation/file_op.h" + +#elif defined FFS_LINUX +#include <zen/scope_guard.h> +#include <sys/stat.h> +#include <gio/gio.h> +#endif + +using namespace zen; + + +namespace +{ +#ifdef FFS_WIN +/* +Performance test: delete 1000 files +------------------------------------ +SHFileOperation - single file 33s +SHFileOperation - multiple files 2,1s +IFileOperation - single file 33s +IFileOperation - multiple files 2,1s + +=> SHFileOperation and IFileOperation have nearly IDENTICAL performance characteristics! + +Nevertheless, let's use IFileOperation for better error reporting! +*/ +const bool useIFileOperation = vistaOrLater(); //caveat: function scope static initialization is not thread-safe in VS 2010! + +//(try to) enhance error messages by showing which processed lock the file +Zstring getLockingProcessNames(const Zstring& filename) //throw(), empty string if none found or error occurred +{ + if (vistaOrLater()) + { + using namespace fileop; + const DllFun<FunType_getLockingProcesses> getLockingProcesses(getDllName(), funName_getLockingProcesses); + const DllFun<FunType_freeString> freeString (getDllName(), funName_freeString); + + if (getLockingProcesses && freeString) + { + const wchar_t* procList = nullptr; + if (getLockingProcesses(filename.c_str(), procList)) + { + ZEN_ON_SCOPE_EXIT(freeString(procList)); + return procList; + } + } + } + return Zstring(); +} +#endif +} + + +bool zen::moveToRecycleBin(const Zstring& filename) //throw FileError +{ + if (!somethingExists(filename)) + return false; //neither file nor any other object with that name existing: no error situation, manual deletion relies on it! + +#ifdef FFS_WIN + //::SetFileAttributes(applyLongPathPrefix(filename).c_str(), FILE_ATTRIBUTE_NORMAL); + //warning: moving long file paths to recycler does not work! + //both ::SHFileOperation() and ::IFileOperation() cannot delete a folder named "System Volume Information" with normal attributes but shamelessly report success + //both ::SHFileOperation() and ::IFileOperation() can't handle \\?\-prefix! + + if (useIFileOperation) //new recycle bin usage: available since Vista + { + using namespace fileop; + const DllFun<FunType_moveToRecycleBin> moveToRecycler(getDllName(), funName_moveToRecycleBin); + const DllFun<FunType_getLastError> getLastError (getDllName(), funName_getLastError); + + if (!moveToRecycler || !getLastError) + throw FileError(replaceCpy(_("Unable to move %x to the Recycle Bin!"), L"%x", fmtFileName(filename)) + L"\n\n" + + replaceCpy(_("Cannot load file %x."), L"%x", fmtFileName(getDllName()))); + + std::vector<const wchar_t*> filenames; + filenames.push_back(filename.c_str()); + + if (!moveToRecycler(&filenames[0], filenames.size())) + { + const std::wstring shortMsg = replaceCpy(_("Unable to move %x to the Recycle Bin!"), L"%x", fmtFileName(filename)); + + //if something is locking our file -> emit better error message! + const Zstring procList = getLockingProcessNames(filename); //throw() + if (!procList.empty()) + throw FileError(shortMsg + L"\n\n" + _("The file is locked by another process:") + L"\n" + procList); + + throw FileError(shortMsg + L"\n\n" + getLastError()); + } + } + else //regular recycle bin usage: available since XP + { + const Zstring& filenameDoubleNull = filename + L'\0'; + + SHFILEOPSTRUCT fileOp = {}; + fileOp.hwnd = nullptr; + fileOp.wFunc = FO_DELETE; + fileOp.pFrom = filenameDoubleNull.c_str(); + fileOp.pTo = nullptr; + fileOp.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_SILENT | FOF_NOERRORUI; + fileOp.fAnyOperationsAborted = false; + fileOp.hNameMappings = nullptr; + fileOp.lpszProgressTitle = nullptr; + + //"You should use fully-qualified path names with this function. Using it with relative path names is not thread safe." + if (::SHFileOperation(&fileOp) != 0 || fileOp.fAnyOperationsAborted) + { + throw FileError(replaceCpy(_("Unable to move %x to the Recycle Bin!"), L"%x", fmtFileName(filename))); + } + } + +#elif defined FFS_LINUX + GFile* file = g_file_new_for_path(filename.c_str()); //never fails according to docu + ZEN_ON_SCOPE_EXIT(g_object_unref(file);) + + GError* error = nullptr; + ZEN_ON_SCOPE_EXIT(if (error) g_error_free(error);); + + if (!g_file_trash(file, nullptr, &error)) + { + const std::wstring shortMsg = replaceCpy(_("Unable to move %x to the Recycle Bin!"), L"%x", fmtFileName(filename)); + + if (!error) + throw FileError(shortMsg + L"\n\n" + L"Unknown error."); + + //implement same behavior as in Windows: if recycler is not existing, delete permanently + if (error->code == G_IO_ERROR_NOT_SUPPORTED) + { + struct stat fileInfo = {}; + if (::lstat(filename.c_str(), &fileInfo) != 0) + return false; + + if (S_ISLNK(fileInfo.st_mode) || S_ISREG(fileInfo.st_mode)) + removeFile(filename); //throw FileError + else if (S_ISDIR(fileInfo.st_mode)) + removeDirectory(filename); //throw FileError + return true; + } + + throw FileError(shortMsg + L"\n\n" + L"Glib Error Code " + numberTo<std::wstring>(error->code) + /* L", " + + g_quark_to_string(error->domain) + */ L": " + utf8CvrtTo<std::wstring>(error->message)); + } +#endif + return true; +} + + +#ifdef FFS_WIN +zen::StatusRecycler zen::recycleBinStatus(const Zstring& pathName) +{ + warn_static("fix XP not working + finish"); + + /* + const bool canUseFastCheckForRecycler = winXpOrLater(); + if (!canUseFastCheckForRecycler) //== "checkForRecycleBin" + return STATUS_REC_UNKNOWN; + + using namespace fileop; + const DllFun<FunType_checkRecycler> checkRecycler(getDllName(), funName_checkRecycler); + + if (!checkRecycler) + return STATUS_REC_UNKNOWN; //actually an error since we're >= XP + + const DWORD bufferSize = MAX_PATH + 1; + std::vector<wchar_t> buffer(bufferSize); + if (!::GetVolumePathName(pathName.c_str(), //__in LPCTSTR lpszFileName, + &buffer[0], //__out LPTSTR lpszVolumePathName, + bufferSize)) //__in DWORD cchBufferLength + return STATUS_REC_UNKNOWN; + + Zstring rootPathPf = appendSeparator(&buffer[0]); + + //search root directories for recycle bin folder... + //we would prefer to use CLSID_RecycleBinManager beginning with Vista... if this interface were documented + + //caveat: "subst"-alias of volume contains "$Recycle.Bin" although it is not available!!! + warn_static("check") + + WIN32_FIND_DATA dataRoot = {}; + HANDLE hFindRoot = ::FindFirstFile(applyLongPathPrefix(rootPathPf + L'*').c_str(), &dataRoot); + if (hFindRoot == INVALID_HANDLE_VALUE) + return STATUS_REC_UNKNOWN; + ZEN_ON_SCOPE_EXIT(FindClose(hFindRoot)); + + auto shouldSkip = [](const Zstring& shortname) { return shortname == L"." || shortname == L".."; }; + + do + { + if (!shouldSkip(dataRoot.cFileName) && + (dataRoot.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0 && + (dataRoot.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM ) != 0 && //maybe a little risky to rely on these attributes, there may be a recycler + (dataRoot.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN ) != 0) //(in obscure cases) we don't find, but that's better than the other way round + { + WIN32_FIND_DATA dataChild = {}; + const Zstring childDirPf = rootPathPf + dataRoot.cFileName + L"\\"; + + HANDLE hFindChild = ::FindFirstFile(applyLongPathPrefix(childDirPf + L'*').c_str(), &dataChild); + if (hFindChild != INVALID_HANDLE_VALUE) //if we can't access a subdir, it's probably not the recycler + { + ZEN_ON_SCOPE_EXIT(FindClose(hFindChild)); + do + { + if (!shouldSkip(dataChild.cFileName) && + (dataChild.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) + { + bool isRecycler = false; + if (checkRecycler((childDirPf + dataChild.cFileName).c_str(), isRecycler)) + { + if (isRecycler) + return STATUS_REC_EXISTS; + } + else assert(false); + } + } + while (::FindNextFile(hFindChild, &dataChild)); //ignore errors other than ERROR_NO_MORE_FILES + } + } + } + while (::FindNextFile(hFindRoot, &dataRoot)); // + + return STATUS_REC_MISSING; + + */ + + const DWORD bufferSize = MAX_PATH + 1; + std::vector<wchar_t> buffer(bufferSize); + if (!::GetVolumePathName(pathName.c_str(), //__in LPCTSTR lpszFileName, + &buffer[0], //__out LPTSTR lpszVolumePathName, + bufferSize)) //__in DWORD cchBufferLength + return STATUS_REC_UNKNOWN; + + const Zstring rootPathPf = appendSeparator(&buffer[0]); + + SHQUERYRBINFO recInfo = {}; + recInfo.cbSize = sizeof(recInfo); + HRESULT rv = ::SHQueryRecycleBin(rootPathPf.c_str(), //__in_opt LPCTSTR pszRootPath, + &recInfo); //__inout LPSHQUERYRBINFO pSHQueryRBInfo + //traverses whole C:\$Recycle.Bin directory each time!!!! + + return rv == S_OK ? STATUS_REC_EXISTS : STATUS_REC_MISSING; +} + +#elif defined FFS_LINUX +/* +We really need access to a similar function to check whether a directory supports trashing and emit a warning if it does not! + +The following function looks perfect, alas it is restricted to local files and to the implementation of GIO only: + + gboolean _g_local_file_has_trash_dir(const char* dirname, dev_t dir_dev); + See: http://www.netmite.com/android/mydroid/2.0/external/bluetooth/glib/gio/glocalfileinfo.h + + Just checking for "G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH" is not correct, since we find in + http://www.netmite.com/android/mydroid/2.0/external/bluetooth/glib/gio/glocalfileinfo.c + + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, + writable && parent_info->has_trash_dir); + + => We're NOT interested in whether the specified folder can be trashed, but whether it supports thrashing its child elements! (Only support, not actual write access!) + This renders G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH useless for this purpose. +*/ +#endif diff --git a/zen/recycler.h b/zen/recycler.h new file mode 100644 index 00000000..952deec2 --- /dev/null +++ b/zen/recycler.h @@ -0,0 +1,48 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#ifndef RECYCLER_H_INCLUDED +#define RECYCLER_H_INCLUDED + +#include <zen/file_error.h> +#include <zen/zstring.h> + +namespace zen +{ +/* +-------------------- +|Recycle Bin Access| +-------------------- + +Windows +------- +Recycler API always available: during runtime either SHFileOperation or IFileOperation (since Vista) will be dynamically selected + +Linux +----- +Compiler flags: `pkg-config --cflags gio-2.0` +Linker flags: `pkg-config --libs gio-2.0` + +Already included in package "gtk+-2.0"! +*/ + +//move a file or folder to Recycle Bin (deletes permanently if recycle is not available) +bool moveToRecycleBin(const Zstring& filename); //throw FileError, return "true" if file/dir was actually deleted + + +#ifdef FFS_WIN +enum StatusRecycler +{ + STATUS_REC_EXISTS, + STATUS_REC_MISSING, + STATUS_REC_UNKNOWN +}; + +StatusRecycler recycleBinStatus(const Zstring& pathName); //test existence of Recycle Bin API for certain path +#endif +} + +#endif // RECYCLER_H_INCLUDED diff --git a/zen/stl_tools.h b/zen/stl_tools.h index e9fe149a..78d99832 100644 --- a/zen/stl_tools.h +++ b/zen/stl_tools.h @@ -159,6 +159,7 @@ template <class K, class V> class hash_map : public std::unordered_map<K, V> {}; #endif //as long as variadic templates are not available in MSVC +template<class T> inline std::unique_ptr<T> make_unique() { return std::unique_ptr<T>(new T); } template<class T, class Arg1> inline std::unique_ptr<T> make_unique(Arg1&& arg1) { return std::unique_ptr<T>(new T(std::forward<Arg1>(arg1))); } template<class T, class Arg1, class Arg2> inline std::unique_ptr<T> make_unique(Arg1&& arg1, Arg2&& arg2) { return std::unique_ptr<T>(new T(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2))); } template<class T, class Arg1, class Arg2, class Arg3> inline std::unique_ptr<T> make_unique(Arg1&& arg1, Arg2&& arg2, Arg3&& arg3) { return std::unique_ptr<T>(new T(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2), std::forward<Arg3>(arg3))); } diff --git a/zen/string_base.h b/zen/string_base.h index 82793a49..45f65ab8 100644 --- a/zen/string_base.h +++ b/zen/string_base.h @@ -21,7 +21,7 @@ namespace zen /* Allocator Policy: ----------------- - void* allocate(size_t size) //throw (std::bad_alloc) + void* allocate(size_t size) //throw std::bad_alloc void deallocate(void* ptr) size_t calcCapacity(size_t length) */ @@ -29,7 +29,7 @@ class AllocatorOptimalSpeed //exponential growth + min size { public: //::operator new/ ::operator delete show same performance characterisics like malloc()/free()! - static void* allocate(size_t size) { return ::operator new(size); } //throw (std::bad_alloc) + static void* allocate(size_t size) { return ::operator new(size); } //throw std::bad_alloc static void deallocate(void* ptr) { ::operator delete(ptr); } static size_t calcCapacity(size_t length) { return std::max<size_t>(16, length + length / 2); } //any growth rate should not exceed golden ratio: 1.618033989 }; @@ -38,7 +38,7 @@ public: class AllocatorOptimalMemory //no wasted memory, but more reallocations required when manipulating string { public: - static void* allocate(size_t size) { return ::operator new(size); } //throw (std::bad_alloc) + static void* allocate(size_t size) { return ::operator new(size); } //throw std::bad_alloc static void deallocate(void* ptr) { ::operator delete(ptr); } static size_t calcCapacity(size_t length) { return length; } }; @@ -194,7 +194,7 @@ public: Zbase(const Char* source, size_t length); Zbase(const Zbase& source); Zbase(Zbase&& tmp); - explicit Zbase(Char source); //dangerous if implicit: Char buffer[]; return buffer[0]; ups... + explicit Zbase(Char source); //dangerous if implicit: Char buffer[]; return buffer[0]; ups... forgot &, but no error //allow explicit construction from different string type, prevent ambiguity via SFINAE template <class S> explicit Zbase(const S& other, typename S::value_type = 0); ~Zbase(); diff --git a/zen/string_tools.h b/zen/string_tools.h index 85eef5df..04ba1eef 100644 --- a/zen/string_tools.h +++ b/zen/string_tools.h @@ -26,18 +26,19 @@ namespace zen template <class Char> bool isWhiteSpace(Char ch); template <class Char> bool isDigit (Char ch); //not exactly the same as "std::isdigit" -> we consider '0'-'9' only! -template <class S, class T> bool startsWith(const S& str, const T& prefix); //both S and T can be strings or char/wchar_t arrays or simple char/wchar_t -template <class S, class T> bool endsWith (const S& str, const T& postfix); // +template <class S, class T> bool startsWith(const S& str, const T& prefix); // +template <class S, class T> bool endsWith (const S& str, const T& postfix); //both S and T can be strings or char/wchar_t arrays or simple char/wchar_t +template <class S, class T> bool contains (const S& str, const T& term); // -template <class S, class T> S afterLast (const S& str, const T& ch); //returns the whole string if ch not found -template <class S, class T> S beforeLast (const S& str, const T& ch); //returns empty string if ch not found -template <class S, class T> S afterFirst (const S& str, const T& ch); //returns empty string if ch not found -template <class S, class T> S beforeFirst(const S& str, const T& ch); //returns the whole string if ch not found +template <class S, class T> S afterLast (const S& str, const T& term); //returns the whole string if term not found +template <class S, class T> S beforeLast (const S& str, const T& term); //returns empty string if term not found +template <class S, class T> S afterFirst (const S& str, const T& term); //returns empty string if term not found +template <class S, class T> S beforeFirst(const S& str, const T& term); //returns the whole string if term not found template <class S, class T> std::vector<S> split(const S& str, const T& delimiter); template <class S> void trim(S& str, bool fromLeft = true, bool fromRight = true); -template <class S, class T, class U> void replace ( S& str, const T& oldOne, const U& newOne, bool replaceAll = true); -template <class S, class T, class U> S replaceCpy(const S& str, const T& oldOne, const U& newOne, bool replaceAll = true); +template <class S, class T, class U> void replace ( S& str, const T& oldTerm, const U& newTerm, bool replaceAll = true); +template <class S, class T, class U> S replaceCpy(const S& str, const T& oldTerm, const U& newTerm, bool replaceAll = true); //high-performance conversion between numbers and strings template <class S, class T, class Num> S printNumber(const T& format, const Num& number); //format a single number using std::snprintf() @@ -133,42 +134,62 @@ bool endsWith(const S& str, const T& postfix) } -//returns the whole string if ch not found template <class S, class T> inline -S afterLast(const S& str, const T& ch) +bool contains(const S& str, const T& term) +{ + assert_static(IsStringLike<S>::value && IsStringLike<T>::value); + typedef typename GetCharType<S>::Type CharType; + + const size_t strLen = strLength(str); + const size_t termLen = strLength(term); + if (strLen < termLen) + return false; + + const CharType* const strFirst = strBegin(str); + const CharType* const strLast = strFirst + strLen; + const CharType* const termFirst = strBegin(term); + + return std::search(strFirst, strLast, + termFirst, termFirst + termLen) != strLast; +} + + +//returns the whole string if term not found +template <class S, class T> inline +S afterLast(const S& str, const T& term) { assert_static(IsStringLike<T>::value); typedef typename GetCharType<S>::Type CharType; - const size_t chLen = strLength(ch); + const size_t termLen = strLength(term); - const CharType* const strFirst = strBegin(str); - const CharType* const strLast = strFirst + strLength(str); - const CharType* const chFirst = strBegin(ch); + const CharType* const strFirst = strBegin(str); + const CharType* const strLast = strFirst + strLength(str); + const CharType* const termFirst = strBegin(term); const CharType* iter = search_last(strFirst, strLast, - chFirst, chFirst + chLen); + termFirst, termFirst + termLen); if (iter == strLast) return str; - iter += chLen; + iter += termLen; return S(iter, strLast - iter); } -//returns empty string if ch not found +//returns empty string if term not found template <class S, class T> inline -S beforeLast(const S& str, const T& ch) +S beforeLast(const S& str, const T& term) { assert_static(IsStringLike<T>::value); typedef typename GetCharType<S>::Type CharType; - const CharType* const strFirst = strBegin(str); - const CharType* const strLast = strFirst + strLength(str); - const CharType* const chFirst = strBegin(ch); + const CharType* const strFirst = strBegin(str); + const CharType* const strLast = strFirst + strLength(str); + const CharType* const termFirst = strBegin(term); const CharType* iter = search_last(strFirst, strLast, - chFirst, chFirst + strLength(ch)); + termFirst, termFirst + strLength(term)); if (iter == strLast) return S(); @@ -176,40 +197,40 @@ S beforeLast(const S& str, const T& ch) } -//returns empty string if ch not found +//returns empty string if term not found template <class S, class T> inline -S afterFirst(const S& str, const T& ch) +S afterFirst(const S& str, const T& term) { assert_static(IsStringLike<T>::value); typedef typename GetCharType<S>::Type CharType; - const size_t chLen = strLength(ch); - const CharType* const strFirst = strBegin(str); - const CharType* const strLast = strFirst + strLength(str); - const CharType* const chFirst = strBegin(ch); + const size_t termLen = strLength(term); + const CharType* const strFirst = strBegin(str); + const CharType* const strLast = strFirst + strLength(str); + const CharType* const termFirst = strBegin(term); const CharType* iter = std::search(strFirst, strLast, - chFirst, chFirst + chLen); + termFirst, termFirst + termLen); if (iter == strLast) return S(); - iter += chLen; + iter += termLen; return S(iter, strLast - iter); } -//returns the whole string if ch not found +//returns the whole string if term not found template <class S, class T> inline -S beforeFirst(const S& str, const T& ch) +S beforeFirst(const S& str, const T& term) { assert_static(IsStringLike<T>::value); typedef typename GetCharType<S>::Type CharType; - const CharType* const strFirst = strBegin(str); - const CharType* const chFirst = strBegin(ch); + const CharType* const strFirst = strBegin(str); + const CharType* const termFirst = strBegin(term); return S(strFirst, std::search(strFirst, strFirst + strLength(str), - chFirst, chFirst + strLength(ch)) - strFirst); + termFirst, termFirst + strLength(term)) - strFirst); } @@ -262,22 +283,22 @@ typename EnableIf<!HasMember_append<S>::value>::Type stringAppend(S& str, const template <class S, class T, class U> inline -S replaceCpy(const S& str, const T& oldOne, const U& newOne, bool replaceAll) +S replaceCpy(const S& str, const T& oldTerm, const U& newTerm, bool replaceAll) { assert_static(IsStringLike<T>::value && IsStringLike<U>::value); typedef typename GetCharType<S>::Type CharType; - const size_t oldLen = strLength(oldOne); - const size_t newLen = strLength(newOne); + const size_t oldLen = strLength(oldTerm); + const size_t newLen = strLength(newTerm); S output; const CharType* strPos = strBegin(str); const CharType* const strEnd = strPos + strLength(str); - const CharType* const oldBegin = strBegin(oldOne); - const CharType* const newBegin = strBegin(newOne); + const CharType* const oldBegin = strBegin(oldTerm); + const CharType* const newBegin = strBegin(newTerm); for (;;) { @@ -301,9 +322,9 @@ S replaceCpy(const S& str, const T& oldOne, const U& newOne, bool replaceAll) template <class S, class T, class U> inline -void replace(S& str, const T& oldOne, const U& newOne, bool replaceAll) +void replace(S& str, const T& oldTerm, const U& newTerm, bool replaceAll) { - str = replaceCpy(str, oldOne, newOne, replaceAll); + str = replaceCpy(str, oldTerm, newTerm, replaceAll); } diff --git a/zen/symlink_target.h b/zen/symlink_target.h index 006e4ea0..06239b5a 100644 --- a/zen/symlink_target.h +++ b/zen/symlink_target.h @@ -78,7 +78,7 @@ Zstring getSymlinkRawTargetString(const Zstring& linkPath) //throw FileError FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr); if (hLink == INVALID_HANDLE_VALUE) - throw FileError(_("Error resolving symbolic link:") + L"\n\"" + linkPath + L"\"" + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(linkPath)) + L"\n\n" + getLastErrorFormatted()); ZEN_ON_SCOPE_EXIT(::CloseHandle(hLink)); //respect alignment issues... @@ -94,7 +94,7 @@ Zstring getSymlinkRawTargetString(const Zstring& linkPath) //throw FileError bufferSize, //__in DWORD nOutBufferSize, &bytesReturned, //__out_opt LPDWORD lpBytesReturned, nullptr)) //__inout_opt LPOVERLAPPED lpOverlapped - throw FileError(_("Error resolving symbolic link:") + L"\n\"" + linkPath + L"\"" + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(linkPath)) + L"\n\n" + getLastErrorFormatted()); REPARSE_DATA_BUFFER& reparseData = *reinterpret_cast<REPARSE_DATA_BUFFER*>(&buffer[0]); //REPARSE_DATA_BUFFER needs to be artificially enlarged! @@ -110,7 +110,7 @@ Zstring getSymlinkRawTargetString(const Zstring& linkPath) //throw FileError reparseData.MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR)); } else - throw FileError(_("Error resolving symbolic link:") + L"\n\"" + linkPath + L"\"" + L"\n\n" + L"Not a symbolic link or junction!"); + throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(linkPath)) + L"\n\n" + L"Not a symbolic link or junction!"); //absolute symlinks and junctions technically start with \??\ while relative ones do not if (startsWith(output, Zstr("\\??\\"))) @@ -125,7 +125,7 @@ Zstring getSymlinkRawTargetString(const Zstring& linkPath) //throw FileError const int bytesWritten = ::readlink(linkPath.c_str(), &buffer[0], BUFFER_SIZE); if (bytesWritten < 0 || bytesWritten >= BUFFER_SIZE) { - std::wstring errorMessage = _("Error resolving symbolic link:") + L"\n\"" + linkPath + L"\""; + std::wstring errorMessage = replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(linkPath)); if (bytesWritten < 0) errorMessage += L"\n\n" + getLastErrorFormatted(); throw FileError(errorMessage); diff --git a/zen/thread.h b/zen/thread.h index 4598ea99..ba9a46e2 100644 --- a/zen/thread.h +++ b/zen/thread.h @@ -8,6 +8,8 @@ #define BOOST_THREAD_WRAP_H //temporary solution until C++11 thread becomes fully available +#include <vector> +#include <memory> #ifdef __MINGW32__ #pragma GCC diagnostic push @@ -39,12 +41,31 @@ Example: template <class Function> auto async(Function fun) -> boost::unique_future<decltype(fun())>; +//wait for all with a time limit: return true if *all* results are available! template<class InputIterator, class Duration> -void wait_for_all_timed(InputIterator first, InputIterator last, const Duration& wait_duration); +bool wait_for_all_timed(InputIterator first, InputIterator last, const Duration& wait_duration); +//wait until first job is successful or all failed +template <class T> +class RunUntilFirstHit +{ +public: + RunUntilFirstHit(); + + template <class Fun> + void addJob(Fun f); //f must return a std::unique_ptr<T> containing a value if successful + template <class Duration> + bool timedWait(const Duration& duration) const; //true: "get()" is ready, false: time elapsed + //return first value or none if all jobs failed; blocks until result is ready! + std::unique_ptr<T> get() const; //must be called only once! +private: + class AsyncResult; + std::vector<boost::thread> workload; + std::shared_ptr<AsyncResult> result; +}; @@ -72,21 +93,107 @@ auto async2(Function fun) -> boost::unique_future<T> //workaround VS2010 bug: bo } -template <class Function> inline auto async(Function fun) -> boost::unique_future<decltype(fun())> { return async2<decltype(fun())>(fun); } +template <class Function> inline +auto async(Function fun) -> boost::unique_future<decltype(fun())> { return async2<decltype(fun())>(fun); } template<class InputIterator, class Duration> inline -void wait_for_all_timed(InputIterator first, InputIterator last, const Duration& wait_duration) +bool wait_for_all_timed(InputIterator first, InputIterator last, const Duration& wait_duration) { const boost::system_time endTime = boost::get_system_time() + wait_duration; while (first != last) { - first->timed_wait_until(endTime); - if (boost::get_system_time() >= endTime) - return; + if (!first->timed_wait_until(endTime)) + return false; //time elapsed ++first; } + return true; } + + +template <class T> +class RunUntilFirstHit<T>::AsyncResult +{ +public: + AsyncResult() : +#ifndef NDEBUG + returnedResult(false), +#endif + jobsFinished(0) {} + + //context: worker threads + void reportFinished(std::unique_ptr<T>&& result) + { + { + boost::unique_lock<boost::mutex> dummy(lockResult); + ++jobsFinished; + if (!result_) + result_ = std::move(result); + } + conditionJobDone.notify_one(); + //condition handling, see: http://www.boost.org/doc/libs/1_43_0/doc/html/thread/synchronization.html#thread.synchronization.condvar_ref + } + + //context: main thread + template <class Duration> + bool waitForResult(size_t jobsTotal, const Duration& duration) + { + boost::unique_lock<boost::mutex> dummy(lockResult); + return conditionJobDone.timed_wait(dummy, duration, [&] { return this->jobDone(jobsTotal); }); + //use timed_wait predicate if exitting before condition is reached: http://www.boost.org/doc/libs/1_49_0/doc/html/thread/synchronization.html#thread.synchronization.condvar_ref.condition_variable.timed_wait_rel + } + + std::unique_ptr<T> getResult(size_t jobsTotal) + { + boost::unique_lock<boost::mutex> dummy(lockResult); + + while (!jobDone(jobsTotal)) + conditionJobDone.timed_wait(dummy, boost::posix_time::milliseconds(50)); //interruption point! + +#ifndef NDEBUG + assert(!returnedResult); + returnedResult = true; +#endif + return std::move(result_); + } + +private: + bool jobDone(size_t jobsTotal) const { return result_ || (jobsFinished >= jobsTotal); } //call while locked! +#ifndef NDEBUG + bool returnedResult; +#endif + + boost::mutex lockResult; + size_t jobsFinished; // + std::unique_ptr<T> result_; //our condition is: "have result" or "jobsFinished == jobsTotal" + boost::condition_variable conditionJobDone; +}; + + + +template <class T> inline +RunUntilFirstHit<T>::RunUntilFirstHit() : result(std::make_shared<AsyncResult>()) {} + + +template <class T> +template <class Fun> inline +void RunUntilFirstHit<T>::addJob(Fun f) //f must return a std::unique_ptr<T> containing a value on success +{ + auto result2 = result; //VC11: this is ridiculous!!! + workload.push_back(boost::thread([result2, f] + { + result2->reportFinished(f()); + })); +} + + +template <class T> +template <class Duration> inline +bool RunUntilFirstHit<T>::timedWait(const Duration& duration) const { return result->waitForResult(workload.size(), duration); } + + +template <class T> inline +std::unique_ptr<T> RunUntilFirstHit<T>::get() const { return result->getResult(workload.size()); } } #endif //BOOST_THREAD_WRAP_H diff --git a/zen/tick_count.h b/zen/tick_count.h new file mode 100644 index 00000000..37c7cc59 --- /dev/null +++ b/zen/tick_count.h @@ -0,0 +1,114 @@ +// ************************************************************************** +// * This file is part of the zenXML project. It is distributed under the * +// * Boost Software License, Version 1.0. See accompanying file * +// * LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt. * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#ifndef ZEN_TICK_COUNT_HEADER_3807326 +#define ZEN_TICK_COUNT_HEADER_3807326 + +#include <cstdint> +#include "type_traits.h" +#include "assert_static.h" + +#ifdef FFS_WIN +#include "win.h" //includes "windows.h" +#elif defined FFS_LINUX +#include <time.h> //Posix ::clock_gettime() +#endif + +namespace zen +{ +//a portable "GetTickCount()" using "wall time equivalent" - e.g. no jumps due to ntp time corrections +class TickVal; +std::int64_t operator-(const TickVal& lhs, const TickVal& rhs); + +std::int64_t ticksPerSec(); //return 0 on error +TickVal getTicks(); //return invalid value on error + + + + + + + + + + + + + + + + + + + +//############################ implementation ############################## +class TickVal +{ +public: +#ifdef FFS_WIN + typedef LARGE_INTEGER NativeVal; +#elif defined FFS_LINUX + typedef timespec NativeVal; +#endif + + TickVal() : val_() {} + TickVal(const NativeVal& val) : val_(val) {} + + inline friend + std::int64_t operator-(const TickVal& lhs, const TickVal& rhs) + { +#ifdef FFS_WIN + assert_static(IsSignedInt<decltype(lhs.val_.QuadPart)>::value); + return lhs.val_.QuadPart - rhs.val_.QuadPart; +#elif defined FFS_LINUX + assert_static(IsSignedInt<decltype(lhs.val_.tv_sec)>::value); + assert_static(IsSignedInt<decltype(lhs.val_.tv_nsec)>::value); + return static_cast<std::int64_t>(lhs.val_.tv_sec - rhs.val_.tv_sec) * 1000000000.0 + lhs.val_.tv_nsec - rhs.val_.tv_nsec; +#endif + } + + bool isValid() const { return *this - TickVal() != 0; } + +private: + NativeVal val_; +}; + + +inline +std::int64_t ticksPerSec() //return 0 on error +{ +#ifdef FFS_WIN + LARGE_INTEGER frequency = {}; + if (!::QueryPerformanceFrequency(&frequency)) + return 0; + return frequency.QuadPart; + +#elif defined FFS_LINUX + return 1000000000; //precision: nanoseconds +#endif +} + + +inline +TickVal getTicks() //return 0 on error +{ +#ifdef FFS_WIN + LARGE_INTEGER now = {}; + if (!::QueryPerformanceCounter(&now)) //msdn: SetThreadAffinityMask() may be required if there are bugs in BIOS or HAL" + return TickVal(); + +#elif defined FFS_LINUX + //gettimeofday() seems fine but is deprecated + timespec now = {}; + if (::clock_gettime(CLOCK_MONOTONIC_RAW, &now) != 0) //CLOCK_MONOTONIC measures time reliably across processors! + return TickVal(); +#endif + return now; +} +} + +#endif //ZEN_TICK_COUNT_HEADER_3807326 @@ -11,6 +11,7 @@ #include <ctime> #include "string_tools.h" + namespace zen { struct TimeComp //replaces "struct std::tm" and SYSTEMTIME @@ -26,7 +27,7 @@ struct TimeComp //replaces "struct std::tm" and SYSTEMTIME }; TimeComp localTime (time_t utc = std::time(nullptr)); //convert time_t (UTC) to local time components -time_t localToTimeT(const TimeComp& comp); //convert local time components to time_t (UTC), returns -1 on error +time_t localToTimeT(const TimeComp& comp); //convert local time components to time_t (UTC), returns -1 on error //---------------------------------------------------------------------------------------------------------------------------------- @@ -53,6 +54,9 @@ const struct FormatIsoDateTimeTag {} FORMAT_ISO_DATE_TIME = {}; //%Y-%m-%d %H:%M template <class String> bool parseTime(const String& format, const String& str, TimeComp& comp); //similar to ::strptime(), return true on success +//---------------------------------------------------------------------------------------------------------------------------------- + + diff --git a/zen/win_ver.h b/zen/win_ver.h index 464b7264..c2162c61 100644 --- a/zen/win_ver.h +++ b/zen/win_ver.h @@ -27,35 +27,29 @@ bool win7OrLater(); +//######################### implementation ######################### +//version overview: http://msdn.microsoft.com/en-us/library/ms724834(VS.85).aspx +//2000 is version 5.0 +//XP is version 5.1 +//Server 2003 is version 5.2 +//Vista is version 6.0 +//Seven is version 6.1 - - - - -//######################### implementation ######################### namespace impl { inline -bool winXyOrLater(DWORD major, DWORD minor) //migrate: hold version data as static variable, as soon as C++11 thread safe statics are available in VS +bool winXyOrLater(DWORD major, DWORD minor) { OSVERSIONINFO osvi = {}; osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); - if (::GetVersionEx(&osvi)) + if (::GetVersionEx(&osvi)) //38 ns per call! (yes, that's nano!) -> we do NOT miss C++11 thread safe statics right now... return osvi.dwMajorVersion > major || (osvi.dwMajorVersion == major && osvi.dwMinorVersion >= minor); return false; } } -//version overview: http://msdn.microsoft.com/en-us/library/ms724834(VS.85).aspx - -//2000 is version 5.0 -//XP is version 5.1 -//Server 2003 is version 5.2 -//Vista is version 6.0 -//Seven is version 6.1 - inline bool winXpOrLater() { return impl::winXyOrLater(5, 1); } diff --git a/zen/zstring.cpp b/zen/zstring.cpp index 622fb975..182bad91 100644 --- a/zen/zstring.cpp +++ b/zen/zstring.cpp @@ -67,7 +67,7 @@ std::string LeakChecker::rawMemToString(const void* ptr, size_t size) } -void LeakChecker::reportProblem(const std::string& message) //throw (std::logic_error) +void LeakChecker::reportProblem(const std::string& message) //throw std::logic_error { #ifdef FFS_WIN ::MessageBoxA(nullptr, message.c_str(), "Error", 0); diff --git a/zen/zstring.h b/zen/zstring.h index cb6974e5..8f7486b0 100644 --- a/zen/zstring.h +++ b/zen/zstring.h @@ -47,7 +47,7 @@ private: ~LeakChecker(); static std::string rawMemToString(const void* ptr, size_t size); - void reportProblem(const std::string& message); //throw (std::logic_error) + void reportProblem(const std::string& message); //throw std::logic_error boost::mutex lockActStrings; zen::hash_map<const void*, size_t> activeStrings; @@ -58,7 +58,7 @@ private: class AllocatorFreeStoreChecked { public: - static void* allocate(size_t size) //throw (std::bad_alloc) + static void* allocate(size_t size) //throw std::bad_alloc { #ifndef NDEBUG void* newMem = ::operator new(size); @@ -115,18 +115,19 @@ typedef char Zchar; const Zchar FILE_NAME_SEPARATOR = '/'; #else -#error define platform you are in: FFS_WIN or FFS_LINUX +#error define your platform: FFS_WIN or FFS_LINUX #endif //"The reason for all the fuss above" - Loki/SmartPtr -//a high-performant string for use as file name in multithreaded contexts +//a high-performance string for use as file name in multithreaded contexts typedef zen::Zbase<Zchar, zen::StorageRefCountThreadSafe, AllocatorFreeStoreChecked> Zstring; - - - - +inline +Zstring appendSeparator(Zstring path) //support rvalue references! +{ + return endsWith(path, FILE_NAME_SEPARATOR) ? path : (path += FILE_NAME_SEPARATOR); +} |