diff options
Diffstat (limited to 'zen')
-rw-r--r-- | zen/FindFilePlus/FindFilePlus.vcxproj | 245 | ||||
-rw-r--r-- | zen/FindFilePlus/dll_main.cpp | 30 | ||||
-rw-r--r-- | zen/FindFilePlus/find_file_plus.cpp | 298 | ||||
-rw-r--r-- | zen/FindFilePlus/find_file_plus.h | 78 | ||||
-rw-r--r-- | zen/FindFilePlus/init_dll_binding.h | 16 | ||||
-rw-r--r-- | zen/FindFilePlus/load_dll.cpp | 23 | ||||
-rw-r--r-- | zen/FindFilePlus/load_dll.h | 46 | ||||
-rw-r--r-- | zen/dir_watcher.cpp | 54 | ||||
-rw-r--r-- | zen/dir_watcher.h | 3 | ||||
-rw-r--r-- | zen/file_handling.cpp | 84 | ||||
-rw-r--r-- | zen/file_handling.h | 7 | ||||
-rw-r--r-- | zen/file_id.cpp | 13 | ||||
-rw-r--r-- | zen/file_id.h | 5 | ||||
-rw-r--r-- | zen/file_id_def.h | 65 | ||||
-rw-r--r-- | zen/file_id_internal.h | 48 | ||||
-rw-r--r-- | zen/file_traverser.cpp | 767 | ||||
-rw-r--r-- | zen/file_traverser.h | 7 | ||||
-rw-r--r-- | zen/guid.h | 2 | ||||
-rw-r--r-- | zen/int64.h | 4 | ||||
-rw-r--r-- | zen/long_path_prefix.h | 1 | ||||
-rw-r--r-- | zen/notify_removal.cpp | 41 | ||||
-rw-r--r-- | zen/privilege.cpp | 80 | ||||
-rw-r--r-- | zen/privilege.h | 47 | ||||
-rw-r--r-- | zen/process_status.h | 40 | ||||
-rw-r--r-- | zen/stl_tools.h | 46 | ||||
-rw-r--r-- | zen/symlink_target.h | 2 | ||||
-rw-r--r-- | zen/time.h | 5 |
27 files changed, 1462 insertions, 595 deletions
diff --git a/zen/FindFilePlus/FindFilePlus.vcxproj b/zen/FindFilePlus/FindFilePlus.vcxproj new file mode 100644 index 00000000..2c4256a6 --- /dev/null +++ b/zen/FindFilePlus/FindFilePlus.vcxproj @@ -0,0 +1,245 @@ +<?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"> + <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|x64'">FindFilePlus_$(Platform)</TargetName> + <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">FindFilePlus_$(Platform)</TargetName> + <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">FindFilePlus_$(Platform)</TargetName> + <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">FindFilePlus_$(Platform)</TargetName> + <IncludePath Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">D:\Data\WinDDK\inc\ddk;D:\Data\WinDDK\inc\api;D:\Data\WinDDK\inc\crt;$(WindowsSdkDir)\include;$(VCInstallDir)include</IncludePath> + <IncludePath Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">D:\Data\WinDDK\inc\ddk;D:\Data\WinDDK\inc\api;D:\Data\WinDDK\inc\crt;$(WindowsSdkDir)\include;$(VCInstallDir)include</IncludePath> + <IncludePath Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">D:\Data\WinDDK\inc\ddk;D:\Data\WinDDK\inc\api;D:\Data\WinDDK\inc\crt;$(WindowsSdkDir)\include;$(VCInstallDir)include</IncludePath> + <IncludePath Condition="'$(Configuration)|$(Platform)'=='Release|x64'">D:\Data\WinDDK\inc\ddk;D:\Data\WinDDK\inc\api;D:\Data\WinDDK\inc\crt;$(WindowsSdkDir)\include;$(VCInstallDir)include</IncludePath> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <BuildLog> + <Path>$(IntDir)Build.html</Path> + </BuildLog> + <ClCompile> + <Optimization>Disabled</Optimization> + <PreprocessorDefinitions>_X86_;_DEBUG;_WINDOWS;_USRDLL;FIND_FILE_PLUS_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</DisableSpecificWarnings> + <AdditionalIncludeDirectories>../..</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> + </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>_AMD64_;_DEBUG;_WINDOWS;_USRDLL;FIND_FILE_PLUS_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</DisableSpecificWarnings> + <AdditionalIncludeDirectories>../..</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> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <BuildLog> + <Path>$(IntDir)Build.html</Path> + </BuildLog> + <ClCompile> + <Optimization>MaxSpeed</Optimization> + <IntrinsicFunctions>true</IntrinsicFunctions> + <PreprocessorDefinitions>_X86_;NDEBUG;_WINDOWS;_USRDLL;FIND_FILE_PLUS_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <RuntimeLibrary>MultiThreaded</RuntimeLibrary> + <FunctionLevelLinking>true</FunctionLevelLinking> + <PrecompiledHeader> + </PrecompiledHeader> + <WarningLevel>Level4</WarningLevel> + <SuppressStartupBanner>true</SuppressStartupBanner> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <DisableSpecificWarnings>4100</DisableSpecificWarnings> + <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>../..</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> + </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>_AMD64_;NDEBUG;_WINDOWS;_USRDLL;FIND_FILE_PLUS_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <RuntimeLibrary>MultiThreaded</RuntimeLibrary> + <FunctionLevelLinking>true</FunctionLevelLinking> + <PrecompiledHeader> + </PrecompiledHeader> + <WarningLevel>Level4</WarningLevel> + <SuppressStartupBanner>true</SuppressStartupBanner> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <DisableSpecificWarnings>4100</DisableSpecificWarnings> + <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>../..</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> + </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="find_file_plus.cpp" /> + <ClCompile Include="load_dll.cpp" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="find_file_plus.h" /> + <ClInclude Include="load_dll.h" /> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project>
\ No newline at end of file diff --git a/zen/FindFilePlus/dll_main.cpp b/zen/FindFilePlus/dll_main.cpp new file mode 100644 index 00000000..5d64181b --- /dev/null +++ b/zen/FindFilePlus/dll_main.cpp @@ -0,0 +1,30 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + + +#define WIN32_LEAN_AND_MEAN +#include <zen/win.h> + +#include "init_dll_binding.h" + + +//optional: add init/teardown logic here +BOOL APIENTRY DllMain(HINSTANCE hinstDLL, + DWORD fdwReason, + LPVOID lpvReserved) +{ + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + if (!findplus::initDllBinding()) + return false; + case DLL_PROCESS_DETACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + } + return true; +} diff --git a/zen/FindFilePlus/find_file_plus.cpp b/zen/FindFilePlus/find_file_plus.cpp new file mode 100644 index 00000000..becfe553 --- /dev/null +++ b/zen/FindFilePlus/find_file_plus.cpp @@ -0,0 +1,298 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#include "find_file_plus.h" +#include "init_dll_binding.h" +//#include <windows.h> //these two don't play nice with each other +#include "load_dll.h" +#include <zen/scope_guard.h> + +using namespace dll; +using namespace findplus; + + +namespace +{ +struct FileError +{ + FileError(ULONG errorCode) : win32Error(errorCode) {} + ULONG win32Error; +}; + + +//-------------------------------------------------------------------------------------------------------------- +typedef NTSTATUS (NTAPI* NtOpenFileFunc)(PHANDLE fileHandle, + ACCESS_MASK desiredAccess, + POBJECT_ATTRIBUTES objectAttributes, + PIO_STATUS_BLOCK ioStatusBlock, + ULONG shareAccess, + ULONG openOptions); + +typedef NTSTATUS (NTAPI* NtCloseFunc)(HANDLE handle); + +typedef NTSTATUS (NTAPI* NtQueryDirectoryFileFunc)(HANDLE fileHandle, + HANDLE event, + PIO_APC_ROUTINE apcRoutine, + PVOID apcContext, + PIO_STATUS_BLOCK ioStatusBlock, + PVOID fileInformation, + ULONG length, + FILE_INFORMATION_CLASS fileInformationClass, + BOOLEAN ReturnSingleEntry, + PUNICODE_STRING fileMask, + BOOLEAN restartScan); + +typedef ULONG (NTAPI* RtlNtStatusToDosErrorFunc)(NTSTATUS /*__in status*/); + +typedef struct _RTL_RELATIVE_NAME_U +{ + UNICODE_STRING RelativeName; + HANDLE ContainingDirectory; + PVOID /*PRTLP_CURDIR_REF*/ CurDirRef; +} RTL_RELATIVE_NAME_U, *PRTL_RELATIVE_NAME_U; + +typedef BOOLEAN (NTAPI* RtlDosPathNameToNtPathName_UFunc)(PCWSTR, //__in dosFileName, + PUNICODE_STRING, //__out ntFileName, + PCWSTR*, //__out_optFilePart, + PRTL_RELATIVE_NAME_U); //__out_opt relativeName + +typedef BOOLEAN (NTAPI* RtlDosPathNameToRelativeNtPathName_UFunc)(PCWSTR, //__in dosFileName, + PUNICODE_STRING, //__out ntFileName, + PCWSTR*, //__out_optFilePart, + PRTL_RELATIVE_NAME_U); //__out_opt relativeName + +typedef VOID (NTAPI* RtlFreeUnicodeStringFunc)(PUNICODE_STRING); //__inout unicodeString + +//-------------------------------------------------------------------------------------------------------------- + +//it seems we cannot use any of the ntoskrnl.lib files in WinDDK as they produce access violations +//fortunately dynamic binding works fine: +const SysDllFun<NtOpenFileFunc> ntOpenFile (L"ntdll.dll", "NtOpenFile"); +const SysDllFun<NtCloseFunc> ntClose (L"ntdll.dll", "NtClose"); +const SysDllFun<NtQueryDirectoryFileFunc> ntQueryDirectoryFile (L"ntdll.dll", "NtQueryDirectoryFile"); +const SysDllFun<RtlNtStatusToDosErrorFunc> rtlNtStatusToDosError (L"ntdll.dll", "RtlNtStatusToDosError"); +const SysDllFun<RtlFreeUnicodeStringFunc> rtlFreeUnicodeString (L"ntdll.dll", "RtlFreeUnicodeString"); +const SysDllFun<RtlDosPathNameToNtPathName_UFunc> rtlDosPathNameToNtPathName_U(SysDllFun<RtlDosPathNameToRelativeNtPathName_UFunc>(L"ntdll.dll", "RtlDosPathNameToRelativeNtPathName_U") ? + SysDllFun<RtlDosPathNameToRelativeNtPathName_UFunc>(L"ntdll.dll", "RtlDosPathNameToRelativeNtPathName_U") : //use the newer version if available + SysDllFun<RtlDosPathNameToNtPathName_UFunc>(L"ntdll.dll", "RtlDosPathNameToNtPathName_U")); //fallback for XP +//global constants only -> preserve thread safety! +} + + +bool findplus::initDllBinding() //evaluate in ::DllMain() when attaching process +{ + //NT/ZwXxx Routines + //http://msdn.microsoft.com/en-us/library/ff567122(v=VS.85).aspx + + //Run-Time Library (RTL) Routines + //http://msdn.microsoft.com/en-us/library/ff563638(v=VS.85).aspx + + //verify dynamic dll binding + return ntOpenFile && + ntClose && + ntQueryDirectoryFile && + rtlNtStatusToDosError && + rtlFreeUnicodeString && + rtlDosPathNameToNtPathName_U; + + //this may become handy some time: nt status code STATUS_ORDINAL_NOT_FOUND maps to win32 code ERROR_INVALID_ORDINAL +} + + +class findplus::FileSearcher +{ +public: + FileSearcher(const wchar_t* dirname); //throw FileError + ~FileSearcher(); + + void readdir(FileInformation& output); //throw FileError + +private: + UNICODE_STRING ntPathName; //it seems hDir implicitly keeps a reference to this, at least ::FindFirstFile() does no cleanup before ::FindClose()! + HANDLE hDir; + + ULONG nextEntryOffset; //!= 0 if entry is waiting in buffer + //::FindNextFileW() uses 0x1000 = 4096 = sizeof(FILE_BOTH_DIR_INFORMATION) + sizeof(TCHAR) * 2000 + //=> let's use the same, even if our header is 16 byte larger; maybe there is some packet size advantage for networks? Note that larger buffers seem to degrade performance. + static const ULONG BUFFER_SIZE = 4096; + LONGLONG buffer[BUFFER_SIZE / sizeof(LONGLONG)]; //buffer needs to be aligned at LONGLONG boundary + + static_assert(BUFFER_SIZE % sizeof(LONGLONG) == 0, "ups, our buffer is trimmed!"); +}; + + +FileSearcher::FileSearcher(const wchar_t* dirname) : + hDir(NULL), + nextEntryOffset(0) +{ + ntPathName.Buffer = NULL; + ntPathName.Length = 0; + ntPathName.MaximumLength = 0; + + zen::ScopeGuard guardConstructor = zen::makeGuard([&]() { this->~FileSearcher(); }); + //-------------------------------------------------------------------------------------------------------------- + + //convert dosFileName, e.g. C:\Users or \\?\C:\Users to ntFileName \??\C:\Users + //in contrast to ::FindFirstFile() we don't evaluate the relativeName, however tests indicate ntFileName is *always* filled with an absolute name, even if dosFileName is relative + + //NOTE: RtlDosPathNameToNtPathName_U may be used on all XP/Win7/Win8 for compatibility + // RtlDosPathNameToNtPathName_U: used by Windows XP available with OS version 3.51 (Windows NT) and higher + // RtlDosPathNameToRelativeNtPathName_U: used by Win7/Win8 available with OS version 5.2 (Windows Server 2003) and higher + if (!rtlDosPathNameToNtPathName_U(dirname, //__in dosFileName, + &ntPathName, //__out ntFileName, + NULL, //__out_optFilePart, + NULL)) //__out_opt relativeName - empty if dosFileName is absolute + throw FileError(rtlNtStatusToDosError(STATUS_OBJECT_PATH_NOT_FOUND)); //translates to ERROR_PATH_NOT_FOUND, same behavior like ::FindFirstFileEx() + + OBJECT_ATTRIBUTES objAttr = {}; + InitializeObjectAttributes(&objAttr, //[out] POBJECT_ATTRIBUTES initializedAttributes, + &ntPathName, //[in] PUNICODE_STRING objectName, + OBJ_CASE_INSENSITIVE, //[in] ULONG attributes, + NULL, //[in] HANDLE rootDirectory, + NULL); //[in, optional] PSECURITY_DESCRIPTOR securityDescriptor + + { + IO_STATUS_BLOCK status = {}; + NTSTATUS rv = ntOpenFile(&hDir, //__out PHANDLE FileHandle, + FILE_LIST_DIRECTORY | SYNCHRONIZE, //__in ACCESS_MASK desiredAccess, - 100001 used by ::FindFirstFile() on all XP/Win7/Win8 + &objAttr, //__in POBJECT_ATTRIBUTES objectAttributes, + &status, //__out PIO_STATUS_BLOCK ioStatusBlock, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //__in ULONG shareAccess, - 7 on Win7/Win8, 3 on XP + FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT); //__in ULONG openOptions - 4021 used on all XP/Win7/Win8 + if (!NT_SUCCESS(rv)) + throw FileError(rtlNtStatusToDosError(rv)); + } + + guardConstructor.dismiss(); +} + + +inline +FileSearcher::~FileSearcher() +{ + //cleanup in reverse order + if (hDir) + ntClose(hDir); + + if (ntPathName.Buffer) + rtlFreeUnicodeString(&ntPathName); //cleanup identical to ::FindFirstFile() + //note that most if this function seems inlined by the linker, so that its assembly looks equivalent to "RtlFreeHeap(GetProcessHeap(), 0, ntPathName.Buffer)" +} + + +void FileSearcher::readdir(FileInformation& output) +{ + //although FILE_ID_FULL_DIR_INFORMATION should suffice for our purposes, there are problems on Windows XP for certain directories, e.g. "\\Vboxsvr\build" + //making NtQueryDirectoryFile() return with STATUS_INVALID_PARAMETER while other directories, e.g. "C:\" work fine for some reason + //FILE_ID_BOTH_DIR_INFORMATION on the other hand works on XP/Win7/Win8 + //performance: there is no noticable difference between FILE_ID_BOTH_DIR_INFORMATION and FILE_ID_FULL_DIR_INFORMATION + + /* corresponding first access in ::FindFirstFileW() + + NTSTATUS rv = ntQueryDirectoryFile(hDir, //__in HANDLE fileHandle, + NULL, //__in_opt HANDLE event, + NULL, //__in_opt PIO_APC_ROUTINE apcRoutine, + NULL, //__in_opt PVOID apcContext, + &status, //__out PIO_STATUS_BLOCK ioStatusBlock, + &buffer, //__out_bcount(Length) PVOID fileInformation, + BUFFER_SIZE, //__in ULONG length, ::FindFirstFileW() on all XP/Win7/Win8 uses sizeof(FILE_BOTH_DIR_INFORMATION) + sizeof(TCHAR) * MAX_PATH == 0x268 + FileIdBothDirectoryInformation, //__in FILE_INFORMATION_CLASS fileInformationClass - all XP/Win7/Win8 use "FileBothDirectoryInformation" + true, //__in BOOLEAN returnSingleEntry, + NULL, //__in_opt PUNICODE_STRING mask, + false); //__in BOOLEAN restartScan + */ + + //analog to ::FindNextFileW() with performance optimized access (in contrast to first access in ::FindFirstFileW()) + if (nextEntryOffset == 0) + { + IO_STATUS_BLOCK status = {}; + NTSTATUS rv = ntQueryDirectoryFile(hDir, //__in HANDLE fileHandle, + NULL, //__in_opt HANDLE event, + NULL, //__in_opt PIO_APC_ROUTINE apcRoutine, + NULL, //__in_opt PVOID apcContext, + &status, //__out PIO_STATUS_BLOCK ioStatusBlock, + &buffer, //__out_bcount(Length) PVOID fileInformation, + BUFFER_SIZE, //__in ULONG length, ::FindNextFileW() on all XP/Win7/Win8 uses sizeof(FILE_BOTH_DIR_INFORMATION) + sizeof(TCHAR) * 2000 == 0x1000 + FileIdBothDirectoryInformation, //__in FILE_INFORMATION_CLASS fileInformationClass - all XP/Win7/Win8 use "FileBothDirectoryInformation" + false, //__in BOOLEAN returnSingleEntry, + NULL, //__in_opt PUNICODE_STRING mask, + false); //__in BOOLEAN restartScan + if (!NT_SUCCESS(rv)) + throw FileError(rtlNtStatusToDosError(rv)); //throws STATUS_NO_MORE_FILES when finished + + if (status.Information == 0) //except for the first call to call ::NtQueryDirectoryFile(): + throw FileError(rtlNtStatusToDosError(STATUS_BUFFER_OVERFLOW)); //if buffer size is too small, return value is STATUS_SUCCESS and Information == 0 -> we don't expect this! + } + + const FILE_ID_BOTH_DIR_INFORMATION& dirInfo = *reinterpret_cast<FILE_ID_BOTH_DIR_INFORMATION*>(reinterpret_cast<char*>(buffer) + nextEntryOffset); + + if (dirInfo.NextEntryOffset == 0) + nextEntryOffset = 0; //our offset is relative to the beginning of the buffer + else + nextEntryOffset += dirInfo.NextEntryOffset; + + + auto toFileTime = [](const LARGE_INTEGER & rawTime) -> FILETIME + { + FILETIME tmp = { rawTime.LowPart, rawTime.HighPart }; + return tmp; + }; + + output.creationTime = toFileTime(dirInfo.CreationTime); + output.lastWriteTime = toFileTime(dirInfo.LastWriteTime); + output.fileSize.QuadPart = dirInfo.EndOfFile.QuadPart; + output.fileId.QuadPart = dirInfo.FileId.QuadPart; + output.fileAttributes = dirInfo.FileAttributes; + output.shortNameLength = dirInfo.FileNameLength / sizeof(TCHAR); //FileNameLength is in bytes! + + if (dirInfo.FileNameLength + sizeof(TCHAR) > sizeof(output.shortName)) //this may actually happen if ::NtQueryDirectoryFile() decides to return a + throw FileError(rtlNtStatusToDosError(STATUS_BUFFER_OVERFLOW)); //short name of length MAX_PATH + 1, 0-termination is not required! + + ::memcpy(output.shortName, dirInfo.FileName, dirInfo.FileNameLength); + output.shortName[output.shortNameLength] = 0; //NOTE: FILE_ID_BOTH_DIR_INFORMATION::FileName in general is NOT 0-terminated! It is on XP/Win7, but NOT on Win8! + + static_assert(sizeof(output.creationTime) == sizeof(dirInfo.CreationTime), "dang!"); + static_assert(sizeof(output.lastWriteTime) == sizeof(dirInfo.LastWriteTime), "dang!"); + static_assert(sizeof(output.fileSize) == sizeof(dirInfo.EndOfFile), "dang!"); + static_assert(sizeof(output.fileId) == sizeof(dirInfo.FileId), "dang!"); + static_assert(sizeof(output.fileAttributes) == sizeof(dirInfo.FileAttributes), "dang!"); +} + + +FindHandle findplus::openDir(const wchar_t* dirname) +{ + try + { + return new FileSearcher(dirname); //throw FileError + } + catch (const FileError& err) + { + setWin32Error(err.win32Error); + return NULL; + } +} + + +bool findplus::readDir(FindHandle hnd, FileInformation& output) +{ + try + { + hnd->readdir(output); //throw FileError + return true; + } + catch (const FileError& err) + { + setWin32Error(err.win32Error); + return false; + } +} + + +void findplus::closeDir(FindHandle hnd) +{ + if (hnd) //play a little "nice" + delete hnd; +} diff --git a/zen/FindFilePlus/find_file_plus.h b/zen/FindFilePlus/find_file_plus.h new file mode 100644 index 00000000..72b76dbb --- /dev/null +++ b/zen/FindFilePlus/find_file_plus.h @@ -0,0 +1,78 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef FIND_FIRST_FILE_PLUS_HEADER_087483434 +#define FIND_FIRST_FILE_PLUS_HEADER_087483434 + +#ifdef FIND_FILE_PLUS_DLL_EXPORTS +#define DLL_FUNCTION_DECLARATION extern "C" __declspec(dllexport) +#else +#define DLL_FUNCTION_DECLARATION extern "C" __declspec(dllimport) +#endif + + +#ifdef FIND_FILE_PLUS_DLL_EXPORTS +#include <Ntifs.h> //driver level headers must be included *before* windows api headers! +#endif +#include <windef.h> // +#undef min +#undef max + +#include <zen/build_info.h> + +namespace findplus +{ +/*-------------- + |declarations| + --------------*/ + +struct FileInformation +{ + FILETIME creationTime; + FILETIME lastWriteTime; + ULARGE_INTEGER fileSize; + ULARGE_INTEGER fileId; + DWORD fileAttributes; + DWORD shortNameLength; + WCHAR shortName[MAX_PATH + 1]; //shortName is 0-terminated +}; //no need for #pragma pack -> all members already starting at 4 byte boundary! + +class FileSearcher; +typedef FileSearcher* FindHandle; + +DLL_FUNCTION_DECLARATION +FindHandle openDir(const wchar_t* dirname); //returns NULL on error, call ::GetLastError() +//note: do NOT place an asterisk at end, e.g. C:\SomeDir\*, as one 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 + +DLL_FUNCTION_DECLARATION +void closeDir(FindHandle hnd); + +/*---------- + |typedefs| + ----------*/ +typedef FindHandle (*OpenDirFunc )(const wchar_t* dirname); +typedef bool (*ReadDirFunc )(FindHandle hnd, FileInformation& dirInfo); +typedef void (*CloseDirFunc)(FindHandle hnd); + +/*-------------- + |symbol names| + --------------*/ +//const pointers ensure internal linkage +const char openDirFuncName [] = "openDir"; +const char readDirFuncName [] = "readDir"; +const char closeDirFuncName[] = "closeDir"; + +/*--------------- + |library names| + ---------------*/ +inline const wchar_t* getDllName() { return zen::is64BitBuild ? L"FindFilePlus_x64.dll" : L"FindFilePlus_Win32.dll"; } +} + + +#endif //FIND_FIRST_FILE_PLUS_HEADER_087483434 diff --git a/zen/FindFilePlus/init_dll_binding.h b/zen/FindFilePlus/init_dll_binding.h new file mode 100644 index 00000000..51b32c99 --- /dev/null +++ b/zen/FindFilePlus/init_dll_binding.h @@ -0,0 +1,16 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef INIT_DLL_BINDING_HEADER_ß018356031467832145 +#define INIT_DLL_BINDING_HEADER_ß018356031467832145 + +namespace findplus +{ +//load and check dll binding at startup +bool initDllBinding(); //evaluate in ::DllMain() when attaching process +} + +#endif //INIT_DLL_BINDING_HEADER_ß018356031467832145 diff --git a/zen/FindFilePlus/load_dll.cpp b/zen/FindFilePlus/load_dll.cpp new file mode 100644 index 00000000..20d9a5fe --- /dev/null +++ b/zen/FindFilePlus/load_dll.cpp @@ -0,0 +1,23 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#include "load_dll.h" +#define WIN32_LEAN_AND_MEAN +#include <zen/win.h> + +void* /*FARPROC*/ dll::loadSymbol(const wchar_t* libraryName, const char* functionName) +{ + return ::GetProcAddress(::GetModuleHandle(libraryName), functionName); + //cleanup neither required nor allowed (::FreeLibrary()) + +} +//note: void* and FARPROC function pointer have same binary size on Windows + + +void dll::setWin32Error(unsigned long lastError) +{ + ::SetLastError(lastError); +} diff --git a/zen/FindFilePlus/load_dll.h b/zen/FindFilePlus/load_dll.h new file mode 100644 index 00000000..350de9f8 --- /dev/null +++ b/zen/FindFilePlus/load_dll.h @@ -0,0 +1,46 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef LOAD_DLL_HEADER_0312463214872163832174 +#define LOAD_DLL_HEADER_0312463214872163832174 + +namespace dll +{ +void setWin32Error(unsigned long lastError); + +//NOTE: uses ::GetModuleHandle => call for system DLLs only! +template <class Func> +class SysDllFun +{ +public: + SysDllFun(const wchar_t* systemLibrary, const char* functionName) : + fun(reinterpret_cast<Func>(loadSymbol(systemLibrary, functionName))) {} + + operator Func() const { return fun; } + +private: + Func fun; +}; + + + + + + + + + + + + + + + + +void* /*FARPROC*/ loadSymbol(const wchar_t* libraryName, const char* functionName); +} + +#endif //LOAD_DLL_HEADER_0312463214872163832174 diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index 81e49f89..c980c715 100644 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -134,7 +134,8 @@ public: ReadChangesAsync(const Zstring& directory, //make sure to not leak in thread-unsafe types! const std::shared_ptr<SharedData>& shared) : shared_(shared), - dirname(directory) + dirname(directory), + hDir(INVALID_HANDLE_VALUE) { if (!endsWith(dirname, FILE_NAME_SEPARATOR)) dirname += FILE_NAME_SEPARATOR; @@ -143,8 +144,8 @@ public: //http://msdn.microsoft.com/en-us/library/aa363858(v=vs.85).aspx try { - Privileges::getInstance().ensureActive(SE_BACKUP_NAME); //throw FileError - Privileges::getInstance().ensureActive(SE_RESTORE_NAME); // + activatePrivilege(SE_BACKUP_NAME); //throw FileError + activatePrivilege(SE_RESTORE_NAME); // } catch (const FileError&) {} @@ -155,7 +156,7 @@ public: OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL); - if (hDir == INVALID_HANDLE_VALUE ) + 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())) @@ -253,7 +254,7 @@ public: std::swap(hDir, other.hDir); } - HANDLE getDirHandle() const { return hDir; } //for reading purposes only, don't abuse (e.g. close handle)! + HANDLE getDirHandle() const { return hDir; } //for reading/monitoring purposes only, don't abuse (e.g. close handle)! private: //shared between main and worker: @@ -268,13 +269,9 @@ class HandleVolumeRemoval : public NotifyRequestDeviceRemoval { public: HandleVolumeRemoval(HANDLE hDir, - boost::thread& worker, - const std::shared_ptr<SharedData>& shared, - const Zstring& dirname) : + boost::thread& worker) : NotifyRequestDeviceRemoval(hDir), //throw FileError worker_(worker), - shared_(shared), - dirname_(dirname), removalRequested(false), operationComplete(false) {} @@ -291,16 +288,12 @@ private: worker_.join(); //now hDir should have been released - //report removal as change to main directory - shared_->addChange(dirname_); - removalRequested = true; } //don't throw! + virtual void onRemovalFinished(HANDLE hnd, bool successful) { operationComplete = true; } //throw()! boost::thread& worker_; - std::shared_ptr<SharedData> shared_; - Zstring dirname_; bool removalRequested; bool operationComplete; }; @@ -312,6 +305,7 @@ struct DirWatcher::Pimpl boost::thread worker; std::shared_ptr<SharedData> shared; + Zstring dirname; std::unique_ptr<HandleVolumeRemoval> volRemoval; }; @@ -320,9 +314,10 @@ DirWatcher::DirWatcher(const Zstring& directory) : //throw FileError pimpl_(new Pimpl) { pimpl_->shared = std::make_shared<SharedData>(); + pimpl_->dirname = directory; ReadChangesAsync reader(directory, pimpl_->shared); //throw FileError - pimpl_->volRemoval.reset(new HandleVolumeRemoval(reader.getDirHandle(), pimpl_->worker, pimpl_->shared, directory)); //throw FileError + pimpl_->volRemoval.reset(new HandleVolumeRemoval(reader.getDirHandle(), pimpl_->worker)); //throw FileError pimpl_->worker = boost::thread(std::move(reader)); } @@ -332,22 +327,29 @@ DirWatcher::~DirWatcher() pimpl_->worker.interrupt(); //pimpl_->worker.join(); -> we don't have time to wait... will take ~50ms anyway //caveat: exitting the app may simply kill this thread! +} + + +std::vector<Zstring> DirWatcher::getChanges(const std::function<void()>& processGuiMessages) //throw FileError +{ + std::vector<Zstring> output; - //wait until device removal is confirmed, to (hopefully!) prevent locking hDir again by new watch! + //wait until device removal is confirmed, to prevent locking hDir again by some new watch! if (pimpl_->volRemoval->requestReceived()) { - const boost::system_time maxwait = boost::get_system_time() + boost::posix_time::seconds(3); //HandleVolumeRemoval::finished() not guaranteed! + const boost::system_time maxwait = boost::get_system_time() + boost::posix_time::seconds(15); + //HandleVolumeRemoval::finished() not guaranteed! note: Windows gives unresponsive applications ca. 10 seconds until unmounting the usb stick in worst case while (!pimpl_->volRemoval->finished() && boost::get_system_time() < maxwait) + { + processGuiMessages(); //DBT_DEVICEREMOVECOMPLETE message is sent here! boost::thread::sleep(boost::get_system_time() + boost::posix_time::milliseconds(50)); - } -} - + } -std::vector<Zstring> DirWatcher::getChanges() //throw FileError -{ - std::vector<Zstring> output; - pimpl_->shared->getChanges(output); //throw FileError + output.push_back(pimpl_->dirname); //report removal as change to main directory + } + else //the normal case... + pimpl_->shared->getChanges(output); //throw FileError return output; } @@ -454,7 +456,7 @@ DirWatcher::~DirWatcher() } -std::vector<Zstring> DirWatcher::getChanges() //throw FileError +std::vector<Zstring> DirWatcher::getChanges(const std::function<void()>&) //throw FileError { std::vector<char> buffer(1024 * (sizeof(struct inotify_event) + 16)); diff --git a/zen/dir_watcher.h b/zen/dir_watcher.h index df3c444c..a9898abb 100644 --- a/zen/dir_watcher.h +++ b/zen/dir_watcher.h @@ -9,6 +9,7 @@ #include <vector> #include <memory> +#include <functional> #include "file_error.h" namespace zen @@ -35,7 +36,7 @@ public: ~DirWatcher(); //extract accumulated changes since last call - std::vector<Zstring> getChanges(); //throw FileError + std::vector<Zstring> getChanges(const std::function<void()>& processGuiMessages); //throw FileError private: DirWatcher(const DirWatcher&); diff --git a/zen/file_handling.cpp b/zen/file_handling.cpp index 5d57938a..8523c633 100644 --- a/zen/file_handling.cpp +++ b/zen/file_handling.cpp @@ -15,7 +15,7 @@ #include "assert_static.h" #include <boost/thread/tss.hpp> #include <boost/thread/once.hpp> -#include "file_id_internal.h" +#include "file_id_def.h" #ifdef FFS_WIN #include "privilege.h" @@ -194,12 +194,15 @@ namespace #ifdef FFS_WIN DWORD retrieveVolumeSerial(const Zstring& pathName) //return 0 on error! { - std::vector<wchar_t> buffer(10000); + //note: this even works for network shares: \\share\dirname + + const DWORD BUFFER_SIZE = 10000; + std::vector<wchar_t> buffer(BUFFER_SIZE); //full pathName need not yet exist! if (!::GetVolumePathName(pathName.c_str(), //__in LPCTSTR lpszFileName, &buffer[0], //__out LPTSTR lpszVolumePathName, - static_cast<DWORD>(buffer.size()))) //__in DWORD cchBufferLength + BUFFER_SIZE)) //__in DWORD cchBufferLength return 0; Zstring volumePath = &buffer[0]; @@ -608,7 +611,7 @@ struct RemoveCallbackImpl : public CallbackRemoveDir moveCallback_(moveCallback) {} virtual void notifyFileDeletion(const Zstring& filename) { moveCallback_.requestUiRefresh(sourceDir_); } - virtual void notifyDirDeletion(const Zstring& dirname) { moveCallback_.requestUiRefresh(sourceDir_); } + virtual void notifyDirDeletion (const Zstring& dirname ) { moveCallback_.requestUiRefresh(sourceDir_); } private: RemoveCallbackImpl(const RemoveCallbackImpl&); @@ -620,7 +623,7 @@ private: } -void moveDirectoryImpl(const Zstring& sourceDir, const Zstring& targetDir, bool ignoreExisting, CallbackMoveFile* callback) //throw FileError; +void moveDirectoryImpl(const Zstring& sourceDir, const Zstring& targetDir, bool ignoreExisting, CallbackMoveFile* callback) //throw FileError { //call back once per folder if (callback) @@ -683,7 +686,7 @@ void moveDirectoryImpl(const Zstring& sourceDir, const Zstring& targetDir, bool } -void zen::moveDirectory(const Zstring& sourceDir, const Zstring& targetDir, bool ignoreExisting, CallbackMoveFile* callback) //throw FileError; +void zen::moveDirectory(const Zstring& sourceDir, const Zstring& targetDir, bool ignoreExisting, CallbackMoveFile* callback) //throw FileError { #ifdef FFS_WIN const Zstring& sourceDirFormatted = sourceDir; @@ -999,13 +1002,13 @@ void copyObjectPermissions(const Zstring& source, const Zstring& target, ProcSym try { //enable privilege: required to read/write SACL information - Privileges::getInstance().ensureActive(SE_SECURITY_NAME); //polling allowed... + activatePrivilege(SE_SECURITY_NAME); //polling allowed... //enable privilege: required to copy owner information - Privileges::getInstance().ensureActive(SE_RESTORE_NAME); + activatePrivilege(SE_RESTORE_NAME); //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) - Privileges::getInstance().ensureActive(SE_BACKUP_NAME); + activatePrivilege(SE_BACKUP_NAME); } catch (const FileError& e) { @@ -1416,14 +1419,19 @@ public: throw FileError(errorMsg); } - void setSrcAttr(const FileAttrib& attr) { sourceAttr = attr; } - FileAttrib getSrcAttr() const { assert(sourceAttr.modificationTime != 0); return sourceAttr; } + void setNewAttr(const FileAttrib& attr) { newAttrib = attr; } + + FileAttrib getSrcAttr() const + { + assert(newAttrib.modificationTime != 0); + return newAttrib; + } private: CallbackData(const CallbackData&); CallbackData& operator=(const CallbackData&); - FileAttrib sourceAttr; + FileAttrib newAttrib; std::wstring errorMsg; // bool exceptionInUserCallback; //these two are exclusive! UInt64 bytesTransferredOnException; @@ -1456,19 +1464,27 @@ DWORD CALLBACK copyCallbackInternal( //#################### return source file attributes ################################ if (dwCallbackReason == CALLBACK_STREAM_SWITCH) //called up front for every file (even if 0-sized) { - BY_HANDLE_FILE_INFORMATION fileInfo = {}; - if (!::GetFileInformationByHandle(hSourceFile, &fileInfo)) + BY_HANDLE_FILE_INFORMATION fileInfoSrc = {}; + if (!::GetFileInformationByHandle(hSourceFile, &fileInfoSrc)) { cbd.reportError(_("Error reading file attributes:") + L"\n\"" + cbd.sourceFile_ + L"\"" + L"\n\n" + getLastErrorFormatted()); return PROGRESS_CANCEL; } - const FileAttrib attr = { UInt64(fileInfo.nFileSizeLow, fileInfo.nFileSizeHigh), - toTimeT(fileInfo.ftLastWriteTime) - }; - //extractFileID(fileInfo)); + BY_HANDLE_FILE_INFORMATION fileInfoTrg = {}; + if (!::GetFileInformationByHandle(hDestinationFile, &fileInfoTrg)) + { + cbd.reportError(_("Error reading file attributes:") + L"\n\"" + cbd.targetFile_ + L"\"" + L"\n\n" + getLastErrorFormatted()); + return PROGRESS_CANCEL; + } + + FileAttrib attr; + attr.fileSize = UInt64(fileInfoSrc.nFileSizeLow, fileInfoSrc.nFileSizeHigh); + attr.modificationTime = toTimeT(fileInfoSrc.ftLastWriteTime); + attr.sourceFileId = extractFileID(fileInfoSrc); + attr.targetFileId = extractFileID(fileInfoTrg); - cbd.setSrcAttr(attr); + cbd.setNewAttr(attr); //#################### copy file creation time ################################ FILETIME creationTime = {}; @@ -1527,7 +1543,7 @@ DWORD CALLBACK copyCallbackInternal( void rawCopyWinApi_sub(const Zstring& sourceFile, const Zstring& targetFile, CallbackCopyFile* callback, - FileAttrib* sourceAttr) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked + FileAttrib* newAttrib) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked { zen::ScopeGuard guardTarget = zen::makeGuard([&]() { removeFile(targetFile); }); //transactional behavior: guard just before starting copy, we don't trust ::CopyFileEx(), do we ;) @@ -1601,8 +1617,8 @@ void rawCopyWinApi_sub(const Zstring& sourceFile, throw FileError(errorMessage); } - if (sourceAttr) - *sourceAttr = cbd.getSrcAttr(); + if (newAttrib) + *newAttrib = cbd.getSrcAttr(); { const Int64 modTime = getFileTime(sourceFile, SYMLINK_FOLLOW); //throw FileError @@ -1972,7 +1988,7 @@ void rawCopyWinApi(const Zstring& sourceFile, void rawCopyStream(const Zstring& sourceFile, const Zstring& targetFile, CallbackCopyFile* callback, - FileAttrib* sourceAttr) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting + FileAttrib* newAttrib) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting { zen::ScopeGuard guardTarget = zen::makeGuard([&]() { removeFile(targetFile); }); //transactional behavior: place guard before lifetime of FileOutput try @@ -2012,23 +2028,29 @@ void rawCopyStream(const Zstring& sourceFile, //adapt file modification time: { - struct stat srcInfo = {}; + 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()); - if (sourceAttr) - { - sourceAttr->fileSize = UInt64(srcInfo.st_size); - sourceAttr->modificationTime = srcInfo.st_mtime; - } - - struct utimbuf newTimes = {}; + struct ::utimbuf newTimes = {}; newTimes.actime = srcInfo.st_atime; newTimes.modtime = srcInfo.st_mtime; //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()); + + 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()); + + newAttrib->fileSize = UInt64(srcInfo.st_size); + newAttrib->modificationTime = srcInfo.st_mtime; + newAttrib->sourceFileId = extractFileID(srcInfo); + newAttrib->targetFileId = extractFileID(trgInfo); + } } guardTarget.dismiss(); //target has been created successfully! diff --git a/zen/file_handling.h b/zen/file_handling.h index 02f3e532..8ffe38d0 100644 --- a/zen/file_handling.h +++ b/zen/file_handling.h @@ -10,6 +10,7 @@ #include "zstring.h" #include "file_error.h" #include "int64.h" +#include "file_id_def.h" namespace zen { @@ -68,7 +69,9 @@ void createDirectory(const Zstring& directory); //throw FileError; -> function o struct FileAttrib { UInt64 fileSize; - Int64 modificationTime; //size_t UTC compatible + Int64 modificationTime; //time_t UTC compatible + FileId sourceFileId; + FileId targetFileId; }; void copyFile(const Zstring& sourceFile, //throw FileError, ErrorTargetPathMissing, ErrorFileLocked (Windows-only) @@ -76,7 +79,7 @@ void copyFile(const Zstring& sourceFile, //throw FileError, ErrorTargetPathMissi bool copyFilePermissions, bool transactionalCopy, CallbackCopyFile* callback, //may be NULL - FileAttrib* sourceAttr = NULL); //return current attributes at the time of copy + FileAttrib* newAttrib = NULL); //return current attributes at the time of copy //Note: it MAY happen that copyFile() leaves temp files behind, e.g. temporary network drop. // => clean them up at an appropriate time (automatically set sync directions to delete them). They have the following ending: const Zstring TEMP_FILE_ENDING = Zstr(".ffs_tmp"); diff --git a/zen/file_id.cpp b/zen/file_id.cpp index acbdcd7b..7527062b 100644 --- a/zen/file_id.cpp +++ b/zen/file_id.cpp @@ -5,7 +5,6 @@ // ************************************************************************** #include "file_id.h" -#include "file_id_internal.h" #ifdef FFS_WIN #include "win.h" //includes "windows.h" @@ -17,7 +16,7 @@ #endif -std::string zen::getFileID(const Zstring& filename) +zen::FileId zen::getFileID(const Zstring& filename) { #ifdef FFS_WIN //WARNING: CreateFile() is SLOW, while GetFileInformationByHandle() is cheap! @@ -43,11 +42,11 @@ std::string zen::getFileID(const Zstring& filename) #elif defined FFS_LINUX struct ::stat fileInfo = {}; - if (::lstat(filename.c_str(), &fileInfo) == 0) //lstat() does not follow symlinks + if (::stat(filename.c_str(), &fileInfo) == 0) //stat() follows symlinks return extractFileID(fileInfo); #endif - return std::string(); + return zen::FileId(); } @@ -56,10 +55,10 @@ bool zen::samePhysicalFile(const Zstring& file1, const Zstring& file2) if (EqualFilename()(file1, file2)) //quick check return true; - const std::string id1 = getFileID(file1); - const std::string id2 = getFileID(file2); + const auto id1 = getFileID(file1); + const auto id2 = getFileID(file2); - if (id1.empty() || id2.empty()) + if (id1 == zen::FileId() || id2 == zen::FileId()) return false; return id1 == id2; diff --git a/zen/file_id.h b/zen/file_id.h index f015569d..3fd4c6bb 100644 --- a/zen/file_id.h +++ b/zen/file_id.h @@ -7,6 +7,7 @@ #ifndef FILEID_H_INCLUDED #define FILEID_H_INCLUDED +#include "file_id_def.h" #include "zstring.h" #include <string> @@ -15,8 +16,8 @@ namespace zen { //get unique file id (symbolic link handling: opens the link!!!) -//returns empty string on error! -std::string getFileID(const Zstring& filename); +//returns initial FileId() on error! +FileId getFileID(const Zstring& filename); //test whether two distinct paths point to the same file or directory: // true: paths point to same files/dirs diff --git a/zen/file_id_def.h b/zen/file_id_def.h new file mode 100644 index 00000000..b2029879 --- /dev/null +++ b/zen/file_id_def.h @@ -0,0 +1,65 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef FILE_ID_INTERNAL_HEADER_013287632486321493 +#define FILE_ID_INTERNAL_HEADER_013287632486321493 + +#include <utility> +#include "assert_static.h" + +#ifdef FFS_WIN +#include "win.h" //includes "windows.h" + +#elif defined FFS_LINUX +#include <sys/stat.h> +#endif + + +namespace zen +{ +#ifdef FFS_WIN +typedef std::pair<decltype(BY_HANDLE_FILE_INFORMATION().dwVolumeSerialNumber), decltype(ULARGE_INTEGER().QuadPart)> FileId; //(volume serial number, file ID) + +inline +FileId extractFileID(const BY_HANDLE_FILE_INFORMATION& fileInfo) +{ + ULARGE_INTEGER uint = {}; + uint.HighPart = fileInfo.nFileIndexHigh; + uint.LowPart = fileInfo.nFileIndexLow; + return std::make_pair(fileInfo.dwVolumeSerialNumber, uint.QuadPart); +} + +inline +FileId extractFileID(DWORD dwVolumeSerialNumber, ULARGE_INTEGER fileId) +{ + return std::make_pair(dwVolumeSerialNumber, fileId.QuadPart); +} + +namespace impl +{ +inline +void validate(const FileId& id, const BY_HANDLE_FILE_INFORMATION& fileInfo) +{ + assert_static(sizeof(id.second) == sizeof(fileInfo.nFileIndexHigh) + sizeof(fileInfo.nFileIndexLow)); + assert_static(sizeof(id.first ) == sizeof(DWORD)); + assert_static(sizeof(id.second) == sizeof(ULARGE_INTEGER)); +} +} + +#elif defined FFS_LINUX +namespace impl { typedef struct ::stat StatDummy; } //sigh... + +typedef std::pair<decltype(impl::StatDummy::st_dev), decltype(impl::StatDummy::st_ino)> FileId; //(device id, inode) + +inline +FileId extractFileID(const struct stat& fileInfo) +{ + return std::make_pair(fileInfo.st_dev, fileInfo.st_ino); +} +#endif +} + +#endif //FILE_ID_INTERNAL_HEADER_013287632486321493 diff --git a/zen/file_id_internal.h b/zen/file_id_internal.h deleted file mode 100644 index 492d8432..00000000 --- a/zen/file_id_internal.h +++ /dev/null @@ -1,48 +0,0 @@ -// ************************************************************************** -// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * -// ************************************************************************** - -#ifndef FILE_ID_INTERNAL_HEADER_013287632486321493 -#define FILE_ID_INTERNAL_HEADER_013287632486321493 - -#include <string> - -#ifdef FFS_WIN -#include "win.h" //includes "windows.h" - -#elif defined FFS_LINUX -#include <sys/stat.h> -#endif - -namespace -{ -template <class T> inline -std::string numberToBytes(T number) -{ - const char* rawBegin = reinterpret_cast<const char*>(&number); - return std::string(rawBegin, rawBegin + sizeof(number)); -} - -#ifdef FFS_WIN -inline -std::string extractFileID(const BY_HANDLE_FILE_INFORMATION& fileInfo) -{ - return numberToBytes(fileInfo.dwVolumeSerialNumber) + - numberToBytes(fileInfo.nFileIndexHigh) + - numberToBytes(fileInfo.nFileIndexLow); -} -#elif defined FFS_LINUX -inline -std::string extractFileID(const struct stat& fileInfo) -{ - return numberToBytes(fileInfo.st_dev) + - numberToBytes(fileInfo.st_ino); -} -#endif - -} - - -#endif //FILE_ID_INTERNAL_HEADER_013287632486321493 diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp index 7a2df695..81e70383 100644 --- a/zen/file_traverser.cpp +++ b/zen/file_traverser.cpp @@ -14,8 +14,8 @@ #include "long_path_prefix.h" #include "dst_hack.h" #include "file_update_handle.h" -//#include "dll_loader.h" -//#include "FindFilePlus/find_file_plus.h" +#include "dll.h" +#include "FindFilePlus/find_file_plus.h" #elif defined FFS_LINUX #include <sys/stat.h> @@ -30,33 +30,34 @@ namespace { //implement "retry" in a generic way: -//returns "true" if "cmd" was invoked successfully, "false" if error occured and was ignored -template <class Command> inline //function object with bool operator()(std::wstring& errorMsg), returns "true" on success, "false" on error and writes "errorMsg" in this case -bool tryReportingError(Command cmd, zen::TraverseCallback& callback) +template <class Command> inline //function object expecting to throw FileError if operation fails +void tryReportingError(Command cmd, zen::TraverseCallback& callback) { for (;;) - { - std::wstring errorMsg; - if (cmd(errorMsg)) - return true; - - switch (callback.onError(errorMsg)) + try { - case TraverseCallback::TRAV_ERROR_RETRY: - break; - case TraverseCallback::TRAV_ERROR_IGNORE: - return false; - default: - assert(false); - break; + cmd(); + return; + } + catch (const FileError& e) + { + switch (callback.onError(e.toString())) + { + case TraverseCallback::TRAV_ERROR_RETRY: + break; + case TraverseCallback::TRAV_ERROR_IGNORE: + return; + default: + assert(false); + break; + } } - } } #ifdef FFS_WIN inline -bool setWin32FileInformationFromSymlink(const Zstring& linkName, zen::TraverseCallback::FileInfo& output) +bool extractFileInfoFromSymlink(const Zstring& linkName, zen::TraverseCallback::FileInfo& output) { //open handle to target of symbolic link HANDLE hFile = ::CreateFile(zen::applyLongPathPrefix(linkName).c_str(), @@ -75,288 +76,308 @@ bool setWin32FileInformationFromSymlink(const Zstring& linkName, zen::TraverseCa return false; //write output - output.lastWriteTimeRaw = toTimeT(fileInfoByHandle.ftLastWriteTime); output.fileSize = zen::UInt64(fileInfoByHandle.nFileSizeLow, fileInfoByHandle.nFileSizeHigh); + output.lastWriteTimeRaw = toTimeT(fileInfoByHandle.ftLastWriteTime); + //output.id = extractFileID(fileInfoByHandle); -> id from dereferenced symlink is problematic, since renaming will consider the link, not the target! return true; } + +DWORD retrieveVolumeSerial(const Zstring& pathName) //return 0 on error! +{ + //note: this even works for network shares: \\share\dirname + + const DWORD BUFFER_SIZE = 10000; + std::vector<wchar_t> buffer(BUFFER_SIZE); + + //full pathName need not yet exist! + if (!::GetVolumePathName(pathName.c_str(), //__in LPCTSTR lpszFileName, + &buffer[0], //__out LPTSTR lpszVolumePathName, + BUFFER_SIZE)) //__in DWORD cchBufferLength + return 0; + + Zstring volumePath = &buffer[0]; + if (!endsWith(volumePath, FILE_NAME_SEPARATOR)) + volumePath += FILE_NAME_SEPARATOR; + + DWORD volumeSerial = 0; + if (!::GetVolumeInformation(volumePath.c_str(), //__in_opt LPCTSTR lpRootPathName, + NULL, //__out LPTSTR lpVolumeNameBuffer, + 0, //__in DWORD nVolumeNameSize, + &volumeSerial, //__out_opt LPDWORD lpVolumeSerialNumber, + NULL, //__out_opt LPDWORD lpMaximumComponentLength, + NULL, //__out_opt LPDWORD lpFileSystemFlags, + NULL, //__out LPTSTR lpFileSystemNameBuffer, + 0)) //__in DWORD nFileSystemNameSize + return 0; + + return volumeSerial; +} + + +const DllFun<findplus::OpenDirFunc> openDir (findplus::getDllName(), findplus::openDirFuncName ); // +const DllFun<findplus::ReadDirFunc> readDir (findplus::getDllName(), findplus::readDirFuncName ); //load at startup: avoid pre C++11 static initialization MT issues +const DllFun<findplus::CloseDirFunc> closeDir(findplus::getDllName(), findplus::closeDirFuncName); // + + /* -warn_static("finish") - DllFun<findplus::OpenDirFunc> openDir (findplus::getDllName(), findplus::openDirFuncName); - DllFun<findplus::ReadDirFunc> readDir (findplus::getDllName(), findplus::readDirFuncName); - DllFun<findplus::CloseDirFunc> closeDir(findplus::getDllName(), findplus::closeDirFuncName); - -> thread safety! -*/ -#endif +Common C-style interface for Win32 FindFirstFile(), FindNextFile() and FileFilePlus openDir(), closeDir(): +struct X //see "policy based design" +{ +typedef ... Handle; +typedef ... FindData; +static Handle create(const Zstring& directoryPf, FindData& fileInfo); //throw FileError +static void destroy(Handle hnd); //throw() +static bool next(Handle hnd, const Zstring& directory, WIN32_FIND_DATA& fileInfo) //throw FileError + +//helper routines +static void extractFileInfo (const FindData& fileInfo, const DWORD* volumeSerial, TraverseCallback::FileInfo& output); +static Int64 getModTime (const FindData& fileInfo); +static const FILETIME& getModTimeRaw (const FindData& fileInfo); //yet another concession to DST hack +static const FILETIME& getCreateTimeRaw(const FindData& fileInfo); // +static const wchar_t* getShortName (const FindData& fileInfo); +static bool isDirectory (const FindData& fileInfo); +static bool isSymlink (const FindData& fileInfo); } +Note: Win32 FindFirstFile(), FindNextFile() is a weaker abstraction than FileFilePlus openDir(), readDir(), closeDir() and Unix opendir(), closedir(), stat(), + so unfortunately we have to use former as a greatest common divisor +*/ -class DirTraverser + +struct Win32Traverser { -public: - DirTraverser(const Zstring& baseDirectory, bool followSymlinks, zen::TraverseCallback& sink, zen::DstHackCallback* dstCallback) : -#ifdef FFS_WIN - isFatFileSystem(dst::isFatDrive(baseDirectory)), -#endif - followSymlinks_(followSymlinks) + typedef HANDLE Handle; + typedef WIN32_FIND_DATA FindData; + + static Handle create(const Zstring& directory, FindData& fileInfo) //throw FileError { -#ifdef FFS_WIN - //format base directory name - const Zstring& directoryFormatted = baseDirectory; + const Zstring& directoryPf = endsWith(directory, FILE_NAME_SEPARATOR) ? + directory : + directory + FILE_NAME_SEPARATOR; -#elif defined FFS_LINUX - const Zstring directoryFormatted = //remove trailing slash - baseDirectory.size() > 1 && endsWith(baseDirectory, FILE_NAME_SEPARATOR) ? //exception: allow '/' - beforeLast(baseDirectory, FILE_NAME_SEPARATOR) : - baseDirectory; -#endif + HANDLE output = ::FindFirstFile(applyLongPathPrefix(directoryPf + L'*').c_str(), &fileInfo); + //no noticable performance difference compared to FindFirstFileEx with FindExInfoBasic, FIND_FIRST_EX_CASE_SENSITIVE and/or FIND_FIRST_EX_LARGE_FETCH + if (output == INVALID_HANDLE_VALUE) + throw FileError(_("Error traversing directory:") + L"\n\"" + directory + L"\"" + L"\n\n" + zen::getLastErrorFormatted()); + //::GetLastError() == ERROR_FILE_NOT_FOUND -> actually NOT okay, even for an empty directory this should not occur (., ..) + return output; + } - //traverse directories - traverse(directoryFormatted, sink, 0); + static void destroy(Handle hnd) { ::FindClose(hnd); } //throw() - //apply daylight saving time hack AFTER file traversing, to give separate feedback to user -#ifdef FFS_WIN - if (isFatFileSystem && dstCallback) - applyDstHack(*dstCallback); -#endif + static bool next(Handle hnd, const Zstring& directory, FindData& fileInfo) //throw FileError + { + if (!::FindNextFile(hnd, &fileInfo)) + { + 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()); + } + return true; } -private: - DirTraverser(const DirTraverser&); - DirTraverser& operator=(const DirTraverser&); + //helper routines + template <class FindData> + static void extractFileInfo(const FindData& fileInfo, const DWORD* volumeSerial, TraverseCallback::FileInfo& output) + { + output.lastWriteTimeRaw = getModTime(fileInfo); + output.fileSize = UInt64(fileInfo.nFileSizeLow, fileInfo.nFileSizeHigh); + } - void traverse(const Zstring& directory, zen::TraverseCallback& sink, int level) + template <class FindData> + static Int64 getModTime(const FindData& fileInfo) { return toTimeT(fileInfo.ftLastWriteTime); } + + template <class FindData> + static const FILETIME& getModTimeRaw(const FindData& fileInfo) { return fileInfo.ftLastWriteTime; } + + template <class FindData> + static const FILETIME& getCreateTimeRaw(const FindData& fileInfo) { return fileInfo.ftCreationTime; } + + template <class FindData> + static const wchar_t* getShortName(const FindData& fileInfo) { return fileInfo.cFileName; } + + template <class FindData> + static bool isDirectory(const FindData& fileInfo) { return (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; } + + template <class FindData> + static bool isSymlink(const FindData& fileInfo) { return (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0; } +}; + + +struct FilePlusTraverser +{ + typedef findplus::FindHandle Handle; + typedef findplus::FileInformation FindData; + + static Handle create(const Zstring& directory, FindData& fileInfo) //throw FileError { - tryReportingError([&](std::wstring& errorMsg) -> bool + Handle output = ::openDir(applyLongPathPrefix(directory).c_str()); + if (output == NULL) + throw FileError(_("Error traversing directory:") + L"\n\"" + directory + L"\"" + L"\n\n" + zen::getLastErrorFormatted()); + + bool rv = next(output, directory, fileInfo); //throw FileError + if (!rv) //we expect at least two successful reads, even for an empty directory: ., .. + throw FileError(_("Error traversing directory:") + L"\n\"" + directory + L"\"" + L"\n\n" + zen::getLastErrorFormatted(ERROR_NO_MORE_FILES)); + + return output; + } + + static void destroy(Handle hnd) { ::closeDir(hnd); } //throw() + + static bool next(Handle hnd, const Zstring& directory, FindData& fileInfo) //throw FileError + { + if (!::readDir(hnd, fileInfo)) { - if (level == 100) //notify endless recursion - { - errorMsg = _("Endless loop when traversing directory:") + L"\n\"" + directory + L"\""; + if (::GetLastError() == ERROR_NO_MORE_FILES) //not an error situation return false; - } - return true; - }, sink); + //else we have a problem... report it: + throw FileError(_("Error traversing directory:") + L"\n\"" + directory + L"\"" + L"\n\n" + zen::getLastErrorFormatted()); + } + return true; + } -#ifdef FFS_WIN - /* - //ensure directoryPf ends with backslash - const Zstring& directoryPf = directory.EndsWith(FILE_NAME_SEPARATOR) ? - directory : - directory + FILE_NAME_SEPARATOR; - - FindHandle searchHandle = NULL; - tryReportingError([&](std::wstring& errorMsg) -> bool - { - searchHandle = this->openDir(applyLongPathPrefix(directoryPf).c_str()); - //no noticable performance difference compared to FindFirstFileEx with FindExInfoBasic, FIND_FIRST_EX_CASE_SENSITIVE and/or FIND_FIRST_EX_LARGE_FETCH + //helper routines + template <class FindData> + static void extractFileInfo(const FindData& fileInfo, const DWORD* volumeSerial, TraverseCallback::FileInfo& output) + { + output.fileSize = UInt64(fileInfo.fileSize.QuadPart); + output.lastWriteTimeRaw = getModTime(fileInfo); + if (volumeSerial) + output.id = extractFileID(*volumeSerial, fileInfo.fileId); + } - if (searchHandle == NULL) - { - //const DWORD lastError = ::GetLastError(); - //if (lastError == ERROR_FILE_NOT_FOUND) -> actually NOT okay, even for an empty directory this should not occur (., ..) - //return true; //fine: empty directory + template <class FindData> + static Int64 getModTime(const FindData& fileInfo) { return toTimeT(fileInfo.lastWriteTime); } - //else: we have a problem... report it: - errorMsg = _("Error traversing directory:") + "\n\"" + directory + "\"" + "\n\n" + zen::getLastErrorFormatted(); - return false; - } - return true; - }, sink); + template <class FindData> + static const FILETIME& getModTimeRaw(const FindData& fileInfo) { return fileInfo.lastWriteTime; } - if (searchHandle == NULL) - return; //empty dir or ignore error - ZEN_ON_BLOCK_EXIT(this->closeDir(searchHandle)); + template <class FindData> + static const FILETIME& getCreateTimeRaw(const FindData& fileInfo) { return fileInfo.creationTime; } - FileInformation fileInfo = {}; - for (;;) - { - bool moreData = false; - tryReportingError([&](std::wstring& errorMsg) -> bool - { - if (!this->readDir(searchHandle, fileInfo)) - { - if (::GetLastError() == ERROR_NO_MORE_FILES) //this is fine - return true; - - //else we have a problem... report it: - errorMsg = _("Error traversing directory:") + "\n\"" + directory + "\"" + "\n\n" + zen::getLastErrorFormatted(); - return false; - } - - moreData = true; - return true; - }, sink); - if (!moreData) //no more items or ignore error - return; - - - //don't return "." and ".." - const Zchar* const shortName = fileInfo.shortName; - if (shortName[0] == L'.' && - (shortName[1] == L'\0' || (shortName[1] == L'.' && shortName[2] == L'\0'))) - continue; + template <class FindData> + static const wchar_t* getShortName(const FindData& fileInfo) { return fileInfo.shortName; } - const Zstring& fullName = directoryPf + shortName; + template <class FindData> + static bool isDirectory(const FindData& fileInfo) { return (fileInfo.fileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; } - const bool isSymbolicLink = (fileInfo.fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0; + template <class FindData> + static bool isSymlink(const FindData& fileInfo) { return (fileInfo.fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0; } +}; - if (isSymbolicLink && !followSymlinks_) //evaluate symlink directly - { - TraverseCallback::SymlinkInfo details; - try - { - details.targetPath = getSymlinkRawTargetString(fullName); //throw FileError - } - catch (FileError& e) - { - (void)e; - #ifndef NDEBUG //show broken symlink / access errors in debug build! - sink.onError(e.msg()); - #endif - } - - details.lastWriteTimeRaw = toTimeT(fileInfo.lastWriteTime); - details.dirLink = (fileInfo.fileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; //directory symlinks have this flag on Windows - sink.onSymlink(shortName, fullName, details); - } - else if (fileInfo.fileAttributes & FILE_ATTRIBUTE_DIRECTORY) //a directory... or symlink that needs to be followed (for directory symlinks this flag is set too!) - { - const std::shared_ptr<TraverseCallback> rv = sink.onDir(shortName, fullName); - if (rv) - traverse<followSymlinks_>(fullName, *rv, level + 1); - } - else //a file or symlink that is followed... - { - TraverseCallback::FileInfo details; - - if (isSymbolicLink) //dereference symlinks! - { - if (!setWin32FileInformationFromSymlink(fullName, details)) - { - //broken symlink... - details.lastWriteTimeRaw = 0; //we are not interested in the modification time of the link - details.fileSize = 0U; - } - } - else - { - //####################################### DST hack ########################################### - if (isFatFileSystem) - { - const dst::RawTime rawTime(fileInfo.creationTime, fileInfo.lastWriteTime); - - if (dst::fatHasUtcEncoded(rawTime)) //throw (std::runtime_error) - fileInfo.lastWriteTime = dst::fatDecodeUtcTime(rawTime); //return real UTC time; throw (std::runtime_error) - else - markForDstHack.push_back(std::make_pair(fullName, fileInfo.lastWriteTime)); - } - //####################################### DST hack ########################################### - - details.lastWriteTimeRaw = toTimeT(fileInfo.lastWriteTime); - details.fileSize = fileInfo.fileSize.QuadPart; - } - - sink.onFile(shortName, fullName, details); - } - } - */ +class DirTraverser +{ +public: + DirTraverser(const Zstring& baseDirectory, bool followSymlinks, zen::TraverseCallback& sink, zen::DstHackCallback* dstCallback) : + isFatFileSystem(dst::isFatDrive(baseDirectory)), + followSymlinks_(followSymlinks), + volumeSerial(retrieveVolumeSerial(baseDirectory)) //return 0 on error + { + try //traversing certain folders with restricted permissions requires this privilege! (but copying these files may still fail) + { + activatePrivilege(SE_BACKUP_NAME); //throw FileError + } + catch (...) {} //don't cause issues in user mode + if (::openDir && ::readDir && ::closeDir) + traverse<FilePlusTraverser>(baseDirectory, sink, 0); + else //fallback + traverse<Win32Traverser>(baseDirectory, sink, 0); - //ensure directoryPf ends with backslash - const Zstring& directoryPf = endsWith(directory, FILE_NAME_SEPARATOR) ? - directory : - directory + FILE_NAME_SEPARATOR; - WIN32_FIND_DATA fileInfo = {}; + //apply daylight saving time hack AFTER file traversing, to give separate feedback to user + if (dstCallback && isFatFileSystem) + applyDstHack(*dstCallback); + } - HANDLE searchHandle = INVALID_HANDLE_VALUE; - tryReportingError([&](std::wstring& errorMsg) -> bool +private: + DirTraverser(const DirTraverser&); + DirTraverser& operator=(const DirTraverser&); + + template <class Trav> + void traverse(const Zstring& directory, zen::TraverseCallback& sink, int level) + { + tryReportingError([&] { - searchHandle = ::FindFirstFile(applyLongPathPrefix(directoryPf + L'*').c_str(), &fileInfo); - //no noticable performance difference compared to FindFirstFileEx with FindExInfoBasic, FIND_FIRST_EX_CASE_SENSITIVE and/or FIND_FIRST_EX_LARGE_FETCH + if (level == 100) //notify endless recursion + throw FileError(_("Endless loop when traversing directory:") + L"\n\"" + directory + L"\""); + }, sink); - if (searchHandle == INVALID_HANDLE_VALUE) - { - //const DWORD lastError = ::GetLastError(); - //if (lastError == ERROR_FILE_NOT_FOUND) -> actually NOT okay, even for an empty directory this should not occur (., ..) - //return true; //fine: empty directory + typename Trav::FindData fileInfo = {}; - //else: we have a problem... report it: - errorMsg = _("Error traversing directory:") + L"\n\"" + directory + L"\"" + L"\n\n" + zen::getLastErrorFormatted(); - return false; - } - return true; + typename Trav::Handle searchHandle = 0; + + tryReportingError([&] + { + typedef Trav Trav; //f u VS! + searchHandle = Trav::create(directory, fileInfo); //throw FileError }, sink); - if (searchHandle == INVALID_HANDLE_VALUE) - return; //empty dir or ignore error - ZEN_ON_BLOCK_EXIT(::FindClose(searchHandle)); + if (searchHandle == 0) + return; //ignored error + ZEN_ON_BLOCK_EXIT(typedef Trav Trav; Trav::destroy(searchHandle)); do { //don't return "." and ".." - const Zchar* const shortName = fileInfo.cFileName; + const Zchar* const shortName = Trav::getShortName(fileInfo); if (shortName[0] == L'.' && (shortName[1] == L'\0' || (shortName[1] == L'.' && shortName[2] == L'\0'))) continue; - const Zstring& fullName = directoryPf + shortName; - - const bool isSymbolicLink = (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0; + const Zstring& fullName = endsWith(directory, FILE_NAME_SEPARATOR) ? + directory + shortName : + directory + FILE_NAME_SEPARATOR + shortName; - if (isSymbolicLink && !followSymlinks_) //evaluate symlink directly + if (Trav::isSymlink(fileInfo) && !followSymlinks_) //evaluate symlink directly { TraverseCallback::SymlinkInfo details; try { details.targetPath = getSymlinkRawTargetString(fullName); //throw FileError } - catch (FileError& e) - { - (void)e; -#ifndef NDEBUG //show broken symlink / access errors in debug build! - sink.onError(e.toString()); +#ifdef NDEBUG //Release + catch (FileError&) {} +#else + catch (FileError& e) { sink.onError(e.toString()); } //show broken symlink / access errors in debug build! #endif - } - details.lastWriteTimeRaw = toTimeT(fileInfo.ftLastWriteTime); - details.dirLink = (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; //directory symlinks have this flag on Windows + details.lastWriteTimeRaw = Trav::getModTime (fileInfo); + details.dirLink = Trav::isDirectory(fileInfo); //directory symlinks have this flag on Windows sink.onSymlink(shortName, fullName, details); } - else if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) //a directory... or symlink that needs to be followed (for directory symlinks this flag is set too!) + else if (Trav::isDirectory(fileInfo)) //a directory... or symlink that needs to be followed (for directory symlinks this flag is set too!) { const std::shared_ptr<TraverseCallback> rv = sink.onDir(shortName, fullName); if (rv) - traverse(fullName, *rv, level + 1); + traverse<Trav>(fullName, *rv, level + 1); } else //a file or symlink that is followed... { TraverseCallback::FileInfo details; - if (isSymbolicLink) //dereference symlinks! + if (Trav::isSymlink(fileInfo)) //dereference symlinks! { - if (!setWin32FileInformationFromSymlink(fullName, details)) - { - //broken symlink... - details.lastWriteTimeRaw = 0; //we are not interested in the modification time of the link - details.fileSize = 0U; - } + extractFileInfoFromSymlink(fullName, details); //try to... + //keep details initial if symlink is broken } else { + Trav::extractFileInfo(fileInfo, volumeSerial != 0 ? &volumeSerial : nullptr, details); //make optional character of volumeSerial explicit in the interface + //####################################### DST hack ########################################### if (isFatFileSystem) { - const dst::RawTime rawTime(fileInfo.ftCreationTime, fileInfo.ftLastWriteTime); + const dst::RawTime rawTime(Trav::getCreateTimeRaw(fileInfo), Trav::getModTimeRaw(fileInfo)); if (dst::fatHasUtcEncoded(rawTime)) //throw (std::runtime_error) - fileInfo.ftLastWriteTime = dst::fatDecodeUtcTime(rawTime); //return real UTC time; 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, fileInfo.ftLastWriteTime)); + markForDstHack.push_back(std::make_pair(fullName, Trav::getModTimeRaw(fileInfo))); } //####################################### DST hack ########################################### - details.lastWriteTimeRaw = toTimeT(fileInfo.ftLastWriteTime); - details.fileSize = zen::UInt64(fileInfo.nFileSizeLow, fileInfo.nFileSizeHigh); } sink.onFile(shortName, fullName, details); @@ -366,141 +387,18 @@ private: { bool moreData = false; - tryReportingError([&](std::wstring& errorMsg) -> bool + typedef Trav Trav1; //f u VS! + tryReportingError([&] { - if (!::FindNextFile(searchHandle, // handle to search - &fileInfo)) // pointer to structure for data on found file - { - if (::GetLastError() == ERROR_NO_MORE_FILES) //this is fine - return true; - - //else we have a problem... report it: - errorMsg = _("Error traversing directory:") + L"\n\"" + directory + L"\"" + L"\n\n" + zen::getLastErrorFormatted(); - return false; - } - - moreData = true; - return true; + typedef Trav1 Trav; //f u VS! + moreData = Trav::next(searchHandle, directory, fileInfo); //throw FileError }, sink); return moreData; }()); - -#elif defined FFS_LINUX - DIR* dirObj = NULL; - if (!tryReportingError([&](std::wstring& errorMsg) -> bool - { - dirObj = ::opendir(directory.c_str()); //directory must NOT end with path separator, except "/" - if (dirObj == NULL) - { - errorMsg = _("Error traversing directory:") + L"\n\"" + directory + L"\"" + L"\n\n" + zen::getLastErrorFormatted(); - return false; - } - return true; - }, sink)) - return; - ZEN_ON_BLOCK_EXIT(::closedir(dirObj)); //never close NULL handles! -> crash - - while (true) - { - struct dirent* dirEntry = NULL; - tryReportingError([&](std::wstring& errorMsg) -> bool - { - errno = 0; //set errno to 0 as unfortunately this isn't done when readdir() returns NULL because it can't find any files - dirEntry = ::readdir(dirObj); - if (dirEntry == NULL) - { - if (errno == 0) - return true; //everything okay, not more items - - //else: we have a problem... report it: - errorMsg = _("Error traversing directory:") + L"\n\"" + directory + L"\"" + L"\n\n" + zen::getLastErrorFormatted(); - return false; - } - return true; - }, sink); - if (dirEntry == NULL) //no more items or ignore error - return; - - - //don't return "." and ".." - const char* const shortName = dirEntry->d_name; - if (shortName[0] == '.' && - (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; - - struct stat fileInfo = {}; - - if (!tryReportingError([&](std::wstring& errorMsg) -> bool - { - if (::lstat(fullName.c_str(), &fileInfo) != 0) //lstat() does not resolve symlinks - { - errorMsg = _("Error reading file attributes:") + L"\n\"" + fullName + L"\"" + L"\n\n" + zen::getLastErrorFormatted(); - return false; - } - return true; - }, sink)) - continue; //ignore error: skip file - - const bool isSymbolicLink = S_ISLNK(fileInfo.st_mode); - - if (isSymbolicLink) - { - if (followSymlinks_) //on Linux Symlinks need to be followed to evaluate whether they point to a file or directory - { - if (::stat(fullName.c_str(), &fileInfo) != 0) //stat() resolves symlinks - { - sink.onFile(shortName, fullName, TraverseCallback::FileInfo()); //report broken symlink as file! - continue; - } - } - else //evaluate symlink directly - { - TraverseCallback::SymlinkInfo details; - try - { - details.targetPath = getSymlinkRawTargetString(fullName); //throw FileError - } - catch (FileError& e) - { -#ifndef NDEBUG //show broken symlink / access errors in debug build! - sink.onError(e.toString()); -#endif - } - - details.lastWriteTimeRaw = fileInfo.st_mtime; //UTC time(ANSI C format); unit: 1 second - details.dirLink = ::stat(fullName.c_str(), &fileInfo) == 0 && S_ISDIR(fileInfo.st_mode); //S_ISDIR and S_ISLNK are mutually exclusive on Linux => need to follow link - sink.onSymlink(shortName, fullName, details); - continue; - } - } - - //fileInfo contains dereferenced data in any case from here on - - if (S_ISDIR(fileInfo.st_mode)) //a directory... cannot be a symlink on Linux in this case - { - const std::shared_ptr<TraverseCallback> rv = sink.onDir(shortName, fullName); - if (rv) - traverse(fullName, *rv, level + 1); - } - else //a file... (or symlink; pathological!) - { - TraverseCallback::FileInfo details; - details.lastWriteTimeRaw = fileInfo.st_mtime; //UTC time(ANSI C format); unit: 1 second - details.fileSize = zen::UInt64(fileInfo.st_size); - - sink.onFile(shortName, fullName, details); - } - } -#endif } -#ifdef FFS_WIN //####################################### DST hack ########################################### void applyDstHack(zen::DstHackCallback& dstCallback) { @@ -517,7 +415,7 @@ private: 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, [ = ]() + FileUpdateHandle updateHandle(i->first, [=]() { return ::CreateFile(zen::applyLongPathPrefix(i->first).c_str(), GENERIC_READ | GENERIC_WRITE, //use both when writing over network, see comment in file_io.cpp @@ -571,20 +469,167 @@ private: typedef std::vector<std::pair<Zstring, FILETIME> > FilenameTimeList; FilenameTimeList markForDstHack; //####################################### DST hack ########################################### -#endif + const bool followSymlinks_; + const DWORD volumeSerial; //may be 0! }; -void zen::traverseFolder(const Zstring& directory, bool followSymlinks, TraverseCallback& sink, DstHackCallback* dstCallback) +#elif defined FFS_LINUX +class DirTraverser { -#ifdef FFS_WIN - try //traversing certain folders with restricted permissions requires this privilege! (but copying these files may still fail) +public: + DirTraverser(const Zstring& baseDirectory, bool followSymlinks, zen::TraverseCallback& sink, zen::DstHackCallback* dstCallback) : + followSymlinks_(followSymlinks) { - zen::Privileges::getInstance().ensureActive(SE_BACKUP_NAME); //throw FileError + const Zstring directoryFormatted = //remove trailing slash + baseDirectory.size() > 1 && endsWith(baseDirectory, FILE_NAME_SEPARATOR) ? //exception: allow '/' + beforeLast(baseDirectory, FILE_NAME_SEPARATOR) : + baseDirectory; + + /* quote: "Since POSIX.1 does not specify the size of the d_name field, and other nonstandard fields may precede + that field within the dirent structure, portable applications that use readdir_r() should allocate + the buffer whose address is passed in entry as follows: + len = offsetof(struct dirent, d_name) + pathconf(dirpath, _PC_NAME_MAX) + 1 + entryp = malloc(len); */ + const long maxPath = std::max<long>(::pathconf(directoryFormatted.c_str(), _PC_NAME_MAX), 10000); //::pathconf may return -1 + buffer.resize(offsetof(struct ::dirent, d_name) + maxPath + 1); + + traverse(directoryFormatted, sink, 0); } - catch (...) {} //don't cause issues in user mode + +private: + DirTraverser(const DirTraverser&); + DirTraverser& operator=(const DirTraverser&); + + void traverse(const Zstring& directory, zen::TraverseCallback& sink, int level) + { + tryReportingError([&] + { + if (level == 100) //notify endless recursion + throw FileError(_("Endless loop when traversing directory:") + L"\n\"" + directory + L"\""); + }, sink); + + + DIR* dirObj = NULL; + tryReportingError([&] + { + dirObj = ::opendir(directory.c_str()); //directory must NOT end with path separator, except "/" + if (dirObj == NULL) + throw FileError(_("Error traversing directory:") + L"\n\"" + directory + L"\"" + L"\n\n" + zen::getLastErrorFormatted()); + }, sink); + + if (dirObj == NULL) + return; //ignored error + ZEN_ON_BLOCK_EXIT(::closedir(dirObj)); //never close NULL handles! -> crash + + while (true) + { + struct ::dirent* dirEntry = NULL; + 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()); + }, sink); + if (dirEntry == NULL) //no more items or ignore error + return; + + + //don't return "." and ".." + const char* const shortName = dirEntry->d_name; //evaluate dirEntry *before* going into recursion => we use a single "buffer"! + if (shortName[0] == '.' && + (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; + + 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()); + haveData = true; + }, sink); + if (!haveData) + continue; //ignore error: skip file + + if (S_ISLNK(fileInfo.st_mode)) + { + if (followSymlinks_) //on Linux Symlinks need to be followed to evaluate whether they point to a file or directory + { + if (::stat(fullName.c_str(), &fileInfo) != 0) //stat() resolves symlinks + { + sink.onFile(shortName, fullName, TraverseCallback::FileInfo()); //report broken symlink as file! + continue; + } + + fileInfo.st_dev = 0; //id from dereferenced symlink is problematic, since renaming will consider the link, not the target! + fileInfo.st_ino = 0; // + } + else //evaluate symlink directly + { + TraverseCallback::SymlinkInfo details; + try + { + details.targetPath = getSymlinkRawTargetString(fullName); //throw FileError + } + catch (FileError& e) + { +#ifndef NDEBUG //show broken symlink / access errors in debug build! + sink.onError(e.toString()); #endif + } + + details.lastWriteTimeRaw = fileInfo.st_mtime; //UTC time(ANSI C format); unit: 1 second + details.dirLink = ::stat(fullName.c_str(), &fileInfo) == 0 && S_ISDIR(fileInfo.st_mode); + //S_ISDIR and S_ISLNK are mutually exclusive on Linux => explicitly need to follow link + sink.onSymlink(shortName, fullName, details); + continue; + } + } + + //fileInfo contains dereferenced data in any case from here on + + if (S_ISDIR(fileInfo.st_mode)) //a directory... cannot be a symlink on Linux in this case + { + const std::shared_ptr<TraverseCallback> rv = sink.onDir(shortName, fullName); + if (rv) + traverse(fullName, *rv, level + 1); + } + else //a file... (or symlink; pathological!) + { + TraverseCallback::FileInfo details; + details.fileSize = zen::UInt64(fileInfo.st_size); + details.lastWriteTimeRaw = fileInfo.st_mtime; //UTC time (time_t format); unit: 1 second + details.id = extractFileID(fileInfo); + + sink.onFile(shortName, fullName, details); + } + } + } + std::vector<char> buffer; + const bool followSymlinks_; +}; +#endif +} + + +void zen::traverseFolder(const Zstring& directory, bool followSymlinks, TraverseCallback& sink, DstHackCallback* dstCallback) +{ DirTraverser(directory, followSymlinks, sink, dstCallback); } + + +bool zen::supportForFileId() //Linux: always; Windows: if FindFilePlus_Win32.dll was loaded correctly +{ +#ifdef FFS_WIN + return ::openDir && ::readDir && ::closeDir; + +#elif defined FFS_LINUX + return true; +#endif +} diff --git a/zen/file_traverser.h b/zen/file_traverser.h index 3f4f47d5..075c32e5 100644 --- a/zen/file_traverser.h +++ b/zen/file_traverser.h @@ -10,6 +10,8 @@ #include <memory> #include "zstring.h" #include "int64.h" +#include "file_id_def.h" + //advanced file traverser returning metadata and hierarchical information on files and directories @@ -24,6 +26,7 @@ public: { UInt64 fileSize; //unit: bytes! Int64 lastWriteTimeRaw; //number of seconds since Jan. 1st 1970 UTC + FileId id; //optional: may be initial! }; struct SymlinkInfo @@ -67,6 +70,10 @@ void traverseFolder(const Zstring& directory, //throw(); //followSymlinks: //"true": Symlinks dereferenced and reported via onFile() and onDir() => onSymlink not used! //"false": Symlinks directly reported via onSymlink(), directory symlinks are not followed + + +//determine whether FileId can be expected to be retrieved +bool supportForFileId(); //Linux: always; Windows: if FindFilePlus_Win32.dll was loaded correctly } #endif // FILETRAVERSER_H_INCLUDED @@ -10,7 +10,7 @@ #include <string> #include <boost/uuid/uuid.hpp> -#ifdef __MINGW32__ //boost should start and clean up! +#ifdef __MINGW32__ //boost should start cleaning this mess up #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wshadow" #pragma GCC diagnostic ignored "-Wuninitialized" diff --git a/zen/int64.h b/zen/int64.h index e8a9cbe0..91e24437 100644 --- a/zen/int64.h +++ b/zen/int64.h @@ -56,7 +56,7 @@ public: value(rhs) {} //-> std::int64_t equals long int on x64 Linux! Still we want implicit behavior for all other systems! //unsafe explicit but checked conversion from arbitrary integer type - template <class T> explicit Int64(T rhs) : value(rhs) { checkRange<std::int64_t>(rhs); } + template <class T> explicit Int64(T rhs) : value(static_cast<std::int64_t>(rhs)) { checkRange<std::int64_t>(rhs); } Int64& operator=(const Int64& rhs) { value = rhs.value; return *this; } @@ -135,7 +135,7 @@ public: value(rhs) {} //-> std::uint64_t equals unsigned long int on x64 Linux! Still we want implicit behavior for all other systems! //unsafe explicit but checked conversion from arbitrary integer type - template <class T> explicit UInt64(T rhs) : value(rhs) { checkRange<std::uint64_t>(rhs); } + template <class T> explicit UInt64(T rhs) : value(static_cast<std::uint64_t>(rhs)) { checkRange<std::uint64_t>(rhs); } UInt64& operator=(const UInt64& rhs) { value = rhs.value; return *this; } diff --git a/zen/long_path_prefix.h b/zen/long_path_prefix.h index 1d9213d1..d03409e1 100644 --- a/zen/long_path_prefix.h +++ b/zen/long_path_prefix.h @@ -7,6 +7,7 @@ #ifndef LONGPATHPREFIX_H_INCLUDED #define LONGPATHPREFIX_H_INCLUDED +#include "win.h" #include "zstring.h" namespace zen diff --git a/zen/notify_removal.cpp b/zen/notify_removal.cpp index 8d8eeb90..2b6b9003 100644 --- a/zen/notify_removal.cpp +++ b/zen/notify_removal.cpp @@ -74,11 +74,10 @@ 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 @@ -110,18 +109,17 @@ MessageProvider::MessageProvider() : zen::ScopeGuard guardClass = zen::makeGuard([&]() { ::UnregisterClass(WINDOW_NAME, process); }); //create dummy-window - windowHandle = ::CreateWindow( - WINDOW_NAME, //LPCTSTR lpClassName OR ATOM in low-order word! - NULL, //LPCTSTR lpWindowName, - 0, //DWORD dwStyle, - 0, //int x, - 0, //int y, - 0, //int nWidth, - 0, //int nHeight, - 0, //note: we need a toplevel window to receive device arrival events, not a message-window (HWND_MESSAGE)! - NULL, //HMENU hMenu, - process, //HINSTANCE hInstance, - NULL); //LPVOID lpParam + windowHandle = ::CreateWindow(WINDOW_NAME, //LPCTSTR lpClassName OR ATOM in low-order word! + NULL, //LPCTSTR lpWindowName, + 0, //DWORD dwStyle, + 0, //int x, + 0, //int y, + 0, //int nWidth, + 0, //int nHeight, + 0, //note: we need a toplevel window to receive device arrival events, not a message-window (HWND_MESSAGE)! + NULL, //HMENU hMenu, + process, //HINSTANCE hInstance, + NULL); //LPVOID lpParam if (windowHandle == NULL) throw zen::FileError(std::wstring(L"Could not start monitoring window notifications:") + L"\n\n" + getLastErrorFormatted() + L" (CreateWindow)"); @@ -160,17 +158,16 @@ public: filter.dbch_devicetype = DBT_DEVTYP_HANDLE; filter.dbch_handle = hDir; - hNotification = ::RegisterDeviceNotification( - MessageProvider::instance().getWnd(), //__in HANDLE hRecipient, - &filter, //__in LPVOID NotificationFilter, - DEVICE_NOTIFY_WINDOW_HANDLE); //__in DWORD Flags + hNotification = ::RegisterDeviceNotification(MessageProvider::instance().getWnd(), //__in HANDLE hRecipient, + &filter, //__in LPVOID NotificationFilter, + DEVICE_NOTIFY_WINDOW_HANDLE); //__in DWORD Flags if (hNotification == NULL) { const DWORD lastError = ::GetLastError(); if (lastError != ERROR_CALL_NOT_IMPLEMENTED && //fail on SAMBA share: this shouldn't be a showstopper! lastError != ERROR_SERVICE_SPECIFIC_ERROR && //neither should be fail for "Pogoplug" mapped network drives lastError != ERROR_INVALID_DATA) //this seems to happen for a NetDrive-mapped FTP server - throw zen::FileError(std::wstring(L"Could not register device removal notifications:") + L"\n\n" + getLastErrorFormatted(lastError)); + throw zen::FileError(L"Could not register device removal notifications:" L"\n\n" + getLastErrorFormatted(lastError)); } } diff --git a/zen/privilege.cpp b/zen/privilege.cpp index 809202b7..6dd0b2d7 100644 --- a/zen/privilege.cpp +++ b/zen/privilege.cpp @@ -1,17 +1,15 @@ #include "privilege.h" +#include <map> +#include "thread.h" //includes <boost/thread.hpp> +#include "zstring.h" #include "scope_guard.h" using namespace zen; -Privileges& Privileges::getInstance() +namespace { - static Privileges instance; - return instance; -} - - -bool Privileges::privilegeIsActive(LPCTSTR privilege) //throw FileError +bool privilegeIsActive(LPCTSTR privilege) //throw FileError { HANDLE hToken = NULL; if (!::OpenProcessToken(::GetCurrentProcess(), //__in HANDLE ProcessHandle, @@ -42,7 +40,7 @@ bool Privileges::privilegeIsActive(LPCTSTR privilege) //throw FileError } -void Privileges::setPrivilege(LPCTSTR privilege, bool enable) //throw FileError +void setPrivilege(LPCTSTR privilege, bool enable) //throw FileError { HANDLE hToken = NULL; if (!::OpenProcessToken(::GetCurrentProcess(), //__in HANDLE ProcessHandle, @@ -62,15 +60,67 @@ void Privileges::setPrivilege(LPCTSTR privilege, bool enable) //throw FileError tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = enable ? SE_PRIVILEGE_ENABLED : 0; - if (!::AdjustTokenPrivileges( - hToken, //__in HANDLE TokenHandle, - false, //__in BOOL DisableAllPrivileges, - &tp, //__in_opt PTOKEN_PRIVILEGES NewState, - 0, //__in DWORD BufferLength, - NULL, //__out_opt PTOKEN_PRIVILEGES PreviousState, - NULL)) //__out_opt PDWORD ReturnLength + if (!::AdjustTokenPrivileges(hToken, //__in HANDLE TokenHandle, + false, //__in BOOL DisableAllPrivileges, + &tp, //__in_opt PTOKEN_PRIVILEGES NewState, + 0, //__in DWORD BufferLength, + NULL, //__out_opt PTOKEN_PRIVILEGES PreviousState, + NULL)) //__out_opt PDWORD ReturnLength throw FileError(_("Error setting privilege:") + 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()); } + + +class Privileges +{ +public: + static Privileges& getInstance() + { + static Privileges inst; + return inst; + } + + void ensureActive(LPCTSTR privilege) //throw FileError + { + if (activePrivileges.find(privilege) != activePrivileges.end()) + return; //privilege already active + + if (privilegeIsActive(privilege)) //privilege was already active before starting this tool + activePrivileges.insert(std::make_pair(privilege, false)); + else + { + setPrivilege(privilege, true); + activePrivileges.insert(std::make_pair(privilege, true)); + } + } + +private: + Privileges() {} + Privileges(Privileges&); + void operator=(Privileges&); + + ~Privileges() //clean up: deactivate all privileges that have been activated by this application + { + for (auto iter = activePrivileges.begin(); iter != activePrivileges.end(); ++iter) + if (iter->second) + try + { + setPrivilege(iter->first.c_str(), false); + } + catch (...) {} + } + + std::map<Zstring, bool> activePrivileges; //bool: enabled by this application +}; + +boost::mutex lockPrivileges; +} + + +void zen::activatePrivilege(LPCTSTR privilege) //throw FileError +{ + boost::lock_guard<boost::mutex> dummy(lockPrivileges); + Privileges::getInstance().ensureActive(privilege); +} diff --git a/zen/privilege.h b/zen/privilege.h index 4545aac7..88fc8992 100644 --- a/zen/privilege.h +++ b/zen/privilege.h @@ -7,57 +7,12 @@ #ifndef PRIVILEGE_H_INCLUDED #define PRIVILEGE_H_INCLUDED -#include <map> -#include "zstring.h" #include "file_error.h" #include "win.h" //includes "windows.h" namespace zen { -#ifdef FFS_WIN -class Privileges -{ -public: - static Privileges& getInstance(); - - void ensureActive(LPCTSTR privilege) //throw FileError - { - if (activePrivileges.find(privilege) != activePrivileges.end()) - return; //privilege already active - - if (privilegeIsActive(privilege)) //privilege was already active before starting this tool - activePrivileges.insert(std::make_pair(privilege, false)); - else - { - setPrivilege(privilege, true); - activePrivileges.insert(std::make_pair(privilege, true)); - } - } - -private: - Privileges() {} - Privileges(Privileges&); - void operator=(Privileges&); - - ~Privileges() //clean up: deactivate all privileges that have been activated by this application - { - for (PrivBuffType::const_iterator i = activePrivileges.begin(); i != activePrivileges.end(); ++i) - try - { - if (i->second) - Privileges::setPrivilege(i->first.c_str(), false); - } - catch (...) {} - } - - static bool privilegeIsActive(LPCTSTR privilege); //throw FileError - static void setPrivilege(LPCTSTR privilege, bool enable); //throw FileError - - typedef std::map<Zstring, bool> PrivBuffType; //bool: enabled by this application - - PrivBuffType activePrivileges; -}; -#endif +void activatePrivilege(LPCTSTR privilege); //throw FileError; thread-safe!!! } diff --git a/zen/process_status.h b/zen/process_status.h index cc5825aa..15266b28 100644 --- a/zen/process_status.h +++ b/zen/process_status.h @@ -3,29 +3,55 @@ #ifdef FFS_WIN #include "win.h" //includes "windows.h" + +#elif defined FFS_LINUX #endif namespace zen { -struct DisableStandby //signal a "busy" state to the operating system +struct PreventStandby //signal a "busy" state to the operating system { #ifdef FFS_WIN - DisableStandby() { ::SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED /* | ES_AWAYMODE_REQUIRED*/ ); } - ~DisableStandby() { ::SetThreadExecutionState(ES_CONTINUOUS); } + PreventStandby() { ::SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED /* | ES_AWAYMODE_REQUIRED*/ ); } + ~PreventStandby() { ::SetThreadExecutionState(ES_CONTINUOUS); } #endif }; +struct ScheduleForBackgroundProcessing //lower CPU and file I/O priorities +{ +#ifdef FFS_WIN #ifndef PROCESS_MODE_BACKGROUND_BEGIN #define PROCESS_MODE_BACKGROUND_BEGIN 0x00100000 // Windows Server 2003 and Windows XP/2000: This value is not supported! #define PROCESS_MODE_BACKGROUND_END 0x00200000 // #endif -struct ScheduleForBackgroundProcessing //lower CPU and file I/O priorities -{ -#ifdef FFS_WIN - ScheduleForBackgroundProcessing() { ::SetPriorityClass(::GetCurrentProcess(), PROCESS_MODE_BACKGROUND_BEGIN); } + ScheduleForBackgroundProcessing() { ::SetPriorityClass(::GetCurrentProcess(), PROCESS_MODE_BACKGROUND_BEGIN); } //this call lowers CPU priority, too!! ~ScheduleForBackgroundProcessing() { ::SetPriorityClass(::GetCurrentProcess(), PROCESS_MODE_BACKGROUND_END); } + +#elif defined FFS_LINUX + /* + CPU prio: + int getpriority(PRIO_PROCESS, 0); - errno caveat! + int ::setpriority(PRIO_PROCESS, 0, int prio); //a zero value for "who" denotes the calling process + + priority can be decreased, but NOT increased :( + + file I/O prio: + ScheduleForBackgroundProcessing() : oldIoPrio(::ioprio_get(IOPRIO_WHO_PROCESS, ::getpid())) + { + if (oldIoPrio != -1) + ::ioprio_set(IOPRIO_WHO_PROCESS, ::getpid(), IOPRIO_PRIO_VALUE(IOPRIO_CLASS_IDLE, 0)); + } + ~ScheduleForBackgroundProcessing() + { + if (oldIoPrio != -1) + ::ioprio_set(IOPRIO_WHO_PROCESS, ::getpid(), oldIoPrio); + } + + private: + const int oldIoPrio; + */ #endif }; } diff --git a/zen/stl_tools.h b/zen/stl_tools.h index 1707a1eb..96101821 100644 --- a/zen/stl_tools.h +++ b/zen/stl_tools.h @@ -28,11 +28,11 @@ void map_remove_if(M& map, Predicate p); template <class ForwardIterator, class T, typename Compare> ForwardIterator binary_search(ForwardIterator first, ForwardIterator last, const T& value, Compare comp); -template<class BidirectionalIterator, class T> +template <class BidirectionalIterator, class T> BidirectionalIterator find_last(BidirectionalIterator first, BidirectionalIterator last, const T& value); //replacement for std::find_end taking advantage of bidirectional iterators (and giving the algorithm a reasonable name) -template<class BidirectionalIterator1, class BidirectionalIterator2> +template <class BidirectionalIterator1, class BidirectionalIterator2> BidirectionalIterator1 search_last(BidirectionalIterator1 first1, BidirectionalIterator1 last1, BidirectionalIterator2 first2, BidirectionalIterator2 last2); @@ -96,45 +96,47 @@ ForwardIterator binary_search(ForwardIterator first, ForwardIterator last, const } -template<class BidirectionalIterator, class T> inline +template <class BidirectionalIterator, class T> inline BidirectionalIterator find_last(const BidirectionalIterator first, BidirectionalIterator last, const T& value) { + //reverse iteration: 1. check 2. decrement 3. evaluate const BidirectionalIterator iterNotFound = last; - do + for (;;) //VS 2010 doesn't like "while (true)" { if (last == first) return iterNotFound; --last; + + if (*last == value) + return last; } - while (!(*last == value)); //loop over "last": 1. check 2. decrement 3. evaluate - return last; } -template<class BidirectionalIterator1, class BidirectionalIterator2> inline +template <class BidirectionalIterator1, class BidirectionalIterator2> inline BidirectionalIterator1 search_last(const BidirectionalIterator1 first1, BidirectionalIterator1 last1, const BidirectionalIterator2 first2, BidirectionalIterator2 last2) { - if (first1 == last1 || - first2 == last2) - return last1; - - int diff = static_cast<int>(std::distance(first1, last1)) - - static_cast<int>(std::distance(first2, last2)); - const BidirectionalIterator1 iterNotFound = last1; - --last2; - while (diff-- >= 0) //loop over "last1": 1. check 2. decrement 3. evaluate + //reverse iteration: 1. check 2. decrement 3. evaluate + for (;;) { - --last1; + BidirectionalIterator1 it1 = last1; + BidirectionalIterator2 it2 = last2; - BidirectionalIterator1 iter1 = last1; - for (BidirectionalIterator2 iter2 = last2; *iter1 == *iter2; --iter1, --iter2) - if (iter2 == first2) - return iter1; + for (;;) + { + if (it2 == first2) return it1; + if (it1 == first1) return iterNotFound; + + --it1; + --it2; + + if (*it1 != *it2) break; + } + --last1; } - return iterNotFound; } } diff --git a/zen/symlink_target.h b/zen/symlink_target.h index 370a0c56..dfbbba6d 100644 --- a/zen/symlink_target.h +++ b/zen/symlink_target.h @@ -66,7 +66,7 @@ Zstring getSymlinkRawTargetString(const Zstring& linkPath) //throw FileError try //reading certain symlinks/junctions requires admin rights! This shall not cause an error in user mode! { - Privileges::getInstance().ensureActive(SE_BACKUP_NAME); //throw FileError + activatePrivilege(SE_BACKUP_NAME); //throw FileError } catch (...) {} @@ -175,7 +175,10 @@ String formatTime(const String2& format, const TimeComp& comp, UserDefinedFormat { typedef typename GetCharType<String>::Result CharType; - const struct std::tm& ctc = toClibTimeComponents(comp); + struct std::tm ctc = toClibTimeComponents(comp); + std::mktime (&ctc); // unfortunately std::strftime() needs all elements of "struct tm" filled, e.g. tm_wday, tm_yday + //note: although std::mktime() explicitly expects "local time", calculating weekday and day of year *should* be time-zone and DST independent + CharType buffer[256]; const size_t charsWritten = strftimeWrap(buffer, 256, strBegin(format), &ctc); return String(buffer, charsWritten); |