diff options
Diffstat (limited to 'zen/IFileOperation/file_op.cpp')
-rw-r--r-- | zen/IFileOperation/file_op.cpp | 541 |
1 files changed, 0 insertions, 541 deletions
diff --git a/zen/IFileOperation/file_op.cpp b/zen/IFileOperation/file_op.cpp deleted file mode 100644 index 27a2565b..00000000 --- a/zen/IFileOperation/file_op.cpp +++ /dev/null @@ -1,541 +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) Zenju (zenju AT gmx DOT de) - All Rights Reserved * -// ************************************************************************** - -#include "file_op.h" -#include <algorithm> -#include <string> -#include <vector> - -#define WIN32_LEAN_AND_MEAN -#include <zen/com_ptr.h> -#include <zen/com_error.h> -#include <zen/scope_guard.h> -#include <zen/stl_tools.h> -#include <zen/file_handling.h> - -#include <boost/thread/tss.hpp> - -#include <RestartManager.h> -#pragma comment(lib, "Rstrtmgr.lib") - -#define STRICT_TYPED_ITEMIDS //better type safety for IDLists -#include <Shlobj.h> -#include <shobjidl.h> -#include <shellapi.h> //shell constants such as FO_* values - -using namespace zen; - - -namespace -{ -std::vector<std::wstring> getLockingProcesses(const wchar_t* filename); //throw SysError - - -class RecyclerProgressCallback : public IFileOperationProgressSink -{ - //Sample implementation: %ProgramFiles%\Microsoft SDKs\Windows\v7.1\Samples\winui\shell\appplatform\FileOperationProgressSink - - ~RecyclerProgressCallback() {} //private: do not allow stack usage "thanks" to IUnknown lifetime management! - -public: - RecyclerProgressCallback(fileop::RecyclerCallback callback, void* sink) : - cancellationRequested(false), - callback_(callback), - sink_(sink), - refCount(1) {} - - //IUnknown: reference implementation according to: http://msdn.microsoft.com/en-us/library/office/cc839627.aspx - virtual ULONG STDMETHODCALLTYPE AddRef() - { - return ::InterlockedIncrement(&refCount); - } - - virtual ULONG STDMETHODCALLTYPE Release() - { - ULONG newRefCount = ::InterlockedDecrement(&refCount); - if (newRefCount == 0) //race condition caveat: do NOT check refCount, which might have changed already! - delete this; - return newRefCount; - } - - virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void __RPC_FAR* __RPC_FAR* ppvObject) - { - if (!ppvObject) - return E_INVALIDARG; - - if (riid == IID_IUnknown || riid == IID_IFileOperationProgressSink) - { - *ppvObject = this; - AddRef(); - return S_OK; - } - *ppvObject = NULL; - return E_NOINTERFACE; - } - - //IFileOperationProgressSink - virtual HRESULT STDMETHODCALLTYPE StartOperations() { return S_OK; } - virtual HRESULT STDMETHODCALLTYPE FinishOperations(HRESULT hrResult) { return S_OK; } - virtual HRESULT STDMETHODCALLTYPE PreRenameItem (DWORD dwFlags, __RPC__in_opt IShellItem* psiItem, __RPC__in_opt_string LPCWSTR pszNewName) { return S_OK; } - virtual HRESULT STDMETHODCALLTYPE PostRenameItem (DWORD dwFlags, __RPC__in_opt IShellItem* psiItem, __RPC__in_string LPCWSTR pszNewName, HRESULT hrRename, __RPC__in_opt IShellItem* psiNewlyCreated) { return S_OK; } - virtual HRESULT STDMETHODCALLTYPE PreMoveItem (DWORD dwFlags, __RPC__in_opt IShellItem* psiItem, __RPC__in_opt IShellItem* psiDestinationFolder, __RPC__in_opt_string LPCWSTR pszNewName) { return S_OK; } - virtual HRESULT STDMETHODCALLTYPE PostMoveItem (DWORD dwFlags, __RPC__in_opt IShellItem* psiItem, __RPC__in_opt IShellItem* psiDestinationFolder, __RPC__in_opt_string LPCWSTR pszNewName, HRESULT hrMove, __RPC__in_opt IShellItem* psiNewlyCreated) { return S_OK; } - virtual HRESULT STDMETHODCALLTYPE PreCopyItem (DWORD dwFlags, __RPC__in_opt IShellItem* psiItem, __RPC__in_opt IShellItem* psiDestinationFolder, __RPC__in_opt_string LPCWSTR pszNewName) { return S_OK; } - virtual HRESULT STDMETHODCALLTYPE PostCopyItem (DWORD dwFlags, __RPC__in_opt IShellItem* psiItem, __RPC__in_opt IShellItem* psiDestinationFolder, __RPC__in_opt_string LPCWSTR pszNewName, HRESULT hrCopy, __RPC__in_opt IShellItem* psiNewlyCreated) { return S_OK; } - virtual HRESULT STDMETHODCALLTYPE PreNewItem (DWORD dwFlags, __RPC__in_opt IShellItem* psiDestinationFolder, __RPC__in_opt_string LPCWSTR pszNewName) { return S_OK; } - virtual HRESULT STDMETHODCALLTYPE PostNewItem (DWORD dwFlags, __RPC__in_opt IShellItem* psiDestinationFolder, __RPC__in_opt_string LPCWSTR pszNewName, __RPC__in_opt_string LPCWSTR pszTemplateName, DWORD dwFileAttributes, HRESULT hrNew, __RPC__in_opt IShellItem* psiNewItem) { return S_OK; } - - virtual HRESULT STDMETHODCALLTYPE PreDeleteItem(DWORD dwFlags, __RPC__in_opt IShellItem* psiItem) - { - if (psiItem) - { - LPWSTR itemPath = nullptr; - if (SUCCEEDED(psiItem->GetDisplayName(SIGDN_FILESYSPATH, &itemPath))) //will fail for long file paths > MAX_PATH! - { - ZEN_ON_SCOPE_EXIT(::CoTaskMemFree(itemPath)); - currentItem = itemPath; - } - else if (SUCCEEDED(psiItem->GetDisplayName(SIGDN_NORMALDISPLAY, &itemPath))) //short name only; should work even for long file paths! - { - ZEN_ON_SCOPE_EXIT(::CoTaskMemFree(itemPath)); - currentItem = itemPath; - } - else - currentItem = L"<unknown file>"; //give some indication that file name determination failed (rather than leaving the name empty!) - } - //"Returns S_OK if successful, or an error value otherwise. In the case of an error value, the delete operation - //and all subsequent operations pending from the call to IFileOperation are canceled." - return cancellationRequested ? HRESULT_FROM_WIN32(ERROR_CANCELLED) : S_OK; - } - - virtual HRESULT STDMETHODCALLTYPE PostDeleteItem(DWORD dwFlags, - __RPC__in_opt IShellItem* psiItem, - HRESULT hrDelete, - __RPC__in_opt IShellItem* psiNewlyCreated) - { - if (FAILED(hrDelete)) - lastError = make_unique<std::pair<std::wstring, HRESULT>>(currentItem, hrDelete); - - currentItem.clear(); - //"Returns S_OK if successful, or an error value otherwise. In the case of an error value, - //all subsequent operations pending from the call to IFileOperation are canceled." - return cancellationRequested ? HRESULT_FROM_WIN32(ERROR_CANCELLED) : S_OK; - } - - virtual HRESULT STDMETHODCALLTYPE UpdateProgress(UINT iWorkTotal, UINT iWorkSoFar) - { - if (callback_) - try - { - if (!callback_(currentItem.c_str(), sink_)) //should not throw! - cancellationRequested = true; - } - catch (...) { return E_UNEXPECTED; } - //"If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code." - //-> this probably means, we cannot rely on returning a custom error code here and have IFileOperation::PerformOperations() fail with same - //=> defer cancellation to PreDeleteItem()/PostDeleteItem() - return S_OK; - } - virtual HRESULT STDMETHODCALLTYPE ResetTimer () { return S_OK; } - virtual HRESULT STDMETHODCALLTYPE PauseTimer () { return S_OK; } - virtual HRESULT STDMETHODCALLTYPE ResumeTimer() { return S_OK; } - - //call after IFileOperation::PerformOperations() - const std::pair<std::wstring, HRESULT>* getLastError() const { return lastError.get(); } //(file path, error code) - -private: - std::wstring currentItem; - bool cancellationRequested; - - std::unique_ptr<std::pair<std::wstring, HRESULT>> lastError; - - //file_op user callback - fileop::RecyclerCallback callback_; - void* sink_; - - //support IUnknown - LONG refCount; -}; - - -void moveToRecycleBin(const wchar_t* fileNames[], //throw SysError - size_t fileCount, - fileop::RecyclerCallback callback, - void* sink) -{ - ComPtr<IFileOperation> fileOp; - ZEN_COM_CHECK(::CoCreateInstance(CLSID_FileOperation, //throw SysError - nullptr, - CLSCTX_ALL, - IID_PPV_ARGS(fileOp.init()))); - - // Set the operation flags. Turn off all UI from being shown to the user during the - // operation. This includes error, confirmation and progress dialogs. - ZEN_COM_CHECK(fileOp->SetOperationFlags(FOF_ALLOWUNDO | - FOF_NOCONFIRMATION | - FOF_SILENT | //no progress dialog box - FOF_NOERRORUI | - FOFX_EARLYFAILURE | - //without FOFX_EARLYFAILURE, IFileOperationProgressSink::PostDeleteItem() will always report success, even if deletion failed!!? WTF!? - //PerformOperations() will still succeed but set the uselessly generic GetAnyOperationsAborted() instead :((( - //=> always set FOFX_EARLYFAILURE since we prefer good error messages over "doing as much as possible" - //luckily for FreeFileSync we don't expect failures on individual files anyway: FreeFileSync moves files to be - //deleted to a temporary folder first, so there is no reason why a second move (the recycling itself) should fail - FOF_NO_CONNECTED_ELEMENTS)); - //use FOFX_RECYCLEONDELETE when Windows 8 is available!? - - ComPtr<RecyclerProgressCallback> opProgress; - *opProgress.init() = new (std::nothrow) RecyclerProgressCallback(callback, sink); - if (!opProgress) - throw SysError(formatComError(L"Error creating RecyclerProgressCallback.", E_OUTOFMEMORY)); - - DWORD callbackID = 0; - ZEN_COM_CHECK(fileOp->Advise(opProgress.get(), &callbackID)); - ZEN_ON_SCOPE_EXIT(fileOp->Unadvise(callbackID)); //RecyclerProgressCallback might outlive current scope, so cut access to "callback, sink" - - int operationCount = 0; - - for (size_t i = 0; i < fileCount; ++i) - { - //SHCreateItemFromParsingName() physically checks file existence => callback - if (callback) - { - bool continueExecution = false; - try - { - continueExecution = callback(fileNames[i], sink); //should not throw! - } - catch (...) { throw SysError(formatComError(L"Unexpected exception in callback.", E_UNEXPECTED)); } - - if (!continueExecution) - throw SysError(formatComError(L"Operation cancelled.", HRESULT_FROM_WIN32(ERROR_CANCELLED))); - } - - //create file/folder item object - ComPtr<IShellItem> psiFile; - HRESULT hr = ::SHCreateItemFromParsingName(fileNames[i], - nullptr, - IID_PPV_ARGS(psiFile.init())); - if (FAILED(hr)) - { - if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) || //file not existing anymore - hr == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)) - { - //make sure the file really is not there: Win32 by default strips trailing spaces, so we might end up here in error! - //on the other hand, shell layer does not support \\?\ prefix to prevent this! - if (!somethingExists(fileNames[i])) - continue; - } - throw SysError(formatComError(std::wstring(L"Error calling \"SHCreateItemFromParsingName\" for file:\n") + L"\"" + fileNames[i] + L"\".", hr)); - } - - ZEN_COM_CHECK(fileOp->DeleteItem(psiFile.get(), nullptr)); - - ++operationCount; - } - - if (operationCount == 0) //calling PerformOperations() without anything to do would yield E_UNEXPECTED - return; - - //perform planned operations - try - { - ZEN_COM_CHECK(fileOp->PerformOperations()); - } - catch (const SysError&) - { - //first let's check if we have more detailed error information available - if (const std::pair<std::wstring, HRESULT>* lastError = opProgress->getLastError()) - { - std::vector<std::wstring> processes; //create an even better error message if we detect a locking issue: - try { processes = getLockingProcesses(lastError->first.c_str()); /*throw SysError*/ } - catch (const SysError&) {} - - if (!processes.empty()) - { - std::wstring errorMsg = L"The file \"" + lastError->first + L"\" is locked by another process:"; - std::for_each(processes.begin(), processes.end(), [&](const std::wstring& proc) { errorMsg += L'\n'; errorMsg += proc; }); - throw SysError(errorMsg); //message is descriptive enough, no need to evaluate HRESULT! - } - throw SysError(formatComError(std::wstring(L"Error during \"PerformOperations\" for file:\n") + L"\"" + lastError->first + L"\".", lastError->second)); - } - throw; - } - - //if FOF_NOERRORUI without FOFX_EARLYFAILURE is set, PerformOperations() can return with success despite errors, but sets the following "aborted" flag instead - BOOL pfAnyOperationsAborted = FALSE; - ZEN_COM_CHECK(fileOp->GetAnyOperationsAborted(&pfAnyOperationsAborted)); - - if (pfAnyOperationsAborted == TRUE) - throw SysError(L"Operation did not complete successfully."); -} - - -void copyFile(const wchar_t* sourceFile, //throw SysError - const wchar_t* targetFile) -{ - ComPtr<IFileOperation> fileOp; - ZEN_COM_CHECK(::CoCreateInstance(CLSID_FileOperation, //throw SysError - nullptr, - CLSCTX_ALL, - IID_PPV_ARGS(fileOp.init()))); - - // Set the operation flags. Turn off all UI - // from being shown to the user during the - // operation. This includes error, confirmation - // and progress dialogs. - ZEN_COM_CHECK(fileOp->SetOperationFlags(FOF_NOCONFIRMATION | //throw SysError - FOF_SILENT | - FOFX_EARLYFAILURE | - FOF_NOERRORUI)); - //create source object - ComPtr<IShellItem> psiSourceFile; - { - HRESULT hr = ::SHCreateItemFromParsingName(sourceFile, - nullptr, - IID_PPV_ARGS(psiSourceFile.init())); - if (FAILED(hr)) - throw SysError(formatComError(std::wstring(L"Error calling \"SHCreateItemFromParsingName\" for file:\n") + L"\"" + sourceFile + L"\".", hr)); - } - - const size_t pos = std::wstring(targetFile).find_last_of(L'\\'); - if (pos == std::wstring::npos) - throw SysError(L"Target filename does not contain a path separator."); - - const std::wstring targetFolder(targetFile, pos); - const std::wstring targetFileNameShort = targetFile + pos + 1; - - //create target folder object - ComPtr<IShellItem> psiTargetFolder; - { - HRESULT hr = ::SHCreateItemFromParsingName(targetFolder.c_str(), - nullptr, - IID_PPV_ARGS(psiTargetFolder.init())); - if (FAILED(hr)) - throw SysError(formatComError(std::wstring(L"Error calling \"SHCreateItemFromParsingName\" for folder:\n") + L"\"" + targetFolder + L"\".", hr)); - } - - //schedule file copy operation - ZEN_COM_CHECK(fileOp->CopyItem(psiSourceFile.get(), psiTargetFolder.get(), targetFileNameShort.c_str(), nullptr)); - - //perform actual operations - ZEN_COM_CHECK(fileOp->PerformOperations()); - - //check if errors occured: if FOFX_EARLYFAILURE is not used, PerformOperations() can return with success despite errors! - BOOL pfAnyOperationsAborted = FALSE; - ZEN_COM_CHECK(fileOp->GetAnyOperationsAborted(&pfAnyOperationsAborted)); - - if (pfAnyOperationsAborted == TRUE) - throw SysError(L"Operation did not complete successfully."); -} - - -void getFolderClsid(const wchar_t* dirname, CLSID& pathCLSID) //throw SysError -{ - ComPtr<IShellFolder> desktopFolder; - ZEN_COM_CHECK(::SHGetDesktopFolder(desktopFolder.init())); //throw SysError - - PIDLIST_RELATIVE pidlFolder = nullptr; - ZEN_COM_CHECK(desktopFolder->ParseDisplayName(nullptr, // [in] HWND hwnd, - nullptr, // [in] IBindCtx *pbc, - const_cast<LPWSTR>(dirname), // [in] LPWSTR pszDisplayName, - nullptr, // [out] ULONG *pchEaten, - &pidlFolder, // [out] PIDLIST_RELATIVE* ppidl, - nullptr)); // [in, out] ULONG *pdwAttributes - ZEN_ON_SCOPE_EXIT(::ILFree(pidlFolder)); //older version: ::CoTaskMemFree - - ComPtr<IPersist> persistFolder; - ZEN_COM_CHECK(desktopFolder->BindToObject(pidlFolder, // [in] PCUIDLIST_RELATIVE pidl, - nullptr, // [in] IBindCtx *pbc, - IID_PPV_ARGS(persistFolder.init()))); //throw SysError - - ZEN_COM_CHECK(persistFolder->GetClassID(&pathCLSID)); //throw SysError -} - - -std::vector<std::wstring> getLockingProcesses(const wchar_t* filename) //throw SysError -{ - DWORD sessionHandle = 0; - { - wchar_t sessionKey[CCH_RM_SESSION_KEY + 1] = {}; //fixes two bugs: http://blogs.msdn.com/b/oldnewthing/archive/2012/02/17/10268840.aspx - DWORD rv1 = ::RmStartSession(&sessionHandle, //__out DWORD *pSessionHandle, - 0, //__reserved DWORD dwSessionFlags, - sessionKey); //__out WCHAR strSessionKey[ ] - if (rv1 != ERROR_SUCCESS) - throw SysError(formatSystemError(L"RmStartSession", rv1)); - } - ZEN_ON_SCOPE_EXIT(::RmEndSession(sessionHandle)); - - { - DWORD rv2 = ::RmRegisterResources(sessionHandle, //__in DWORD dwSessionHandle, - 1, //__in UINT nFiles, - &filename, //__in_opt LPCWSTR rgsFilenames[ ], - 0, //__in UINT nApplications, - nullptr, //__in_opt RM_UNIQUE_PROCESS rgApplications[ ], - 0, //__in UINT nServices, - nullptr); //__in_opt LPCWSTR rgsServiceNames[ ] - if (rv2 != ERROR_SUCCESS) - throw SysError(formatSystemError(L"RmRegisterResources", rv2)); - } - - std::vector<RM_PROCESS_INFO> procInfo; - { - UINT procInfoSize = 0; - UINT procInfoSizeNeeded = 0; - DWORD rebootReasons = 0; - DWORD rv3 = ::RmGetList(sessionHandle, &procInfoSizeNeeded, &procInfoSize, nullptr, &rebootReasons); //get procInfoSizeNeeded - if (rv3 == ERROR_SUCCESS) - return std::vector<std::wstring>(); - if (rv3 != ERROR_MORE_DATA) - throw SysError(formatSystemError(L"RmGetList", rv3)); - //C:\pagefile.sys fails with ERROR_SHARING_VIOLATION! - - if (procInfoSizeNeeded == 0) - return std::vector<std::wstring>(); - - procInfoSize = procInfoSizeNeeded; - procInfo.resize(procInfoSizeNeeded); - - rv3 = ::RmGetList(sessionHandle, //__in DWORD dwSessionHandle, - &procInfoSizeNeeded, //__out UINT *pnProcInfoNeeded, - &procInfoSize, //__inout UINT *pnProcInfo, - &procInfo[0], //__inout_opt RM_PROCESS_INFO rgAffectedApps[ ], - &rebootReasons); //__out LPDWORD lpdwRebootReasons - if (rv3 != ERROR_SUCCESS) - throw SysError(formatSystemError(L"RmGetList", rv3)); - procInfo.resize(procInfoSize); - } - - std::vector<std::wstring> output; - for (auto iter = procInfo.begin(); iter != procInfo.end(); ++iter) - { - std::wstring processName = iter->strAppName; - - //try to get process path - HANDLE hProcess = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, //__in DWORD dwDesiredAccess, - false, //__in BOOL bInheritHandle, - iter->Process.dwProcessId); //__in DWORD dwProcessId - if (hProcess) - { - ZEN_ON_SCOPE_EXIT(::CloseHandle(hProcess)); - - FILETIME creationTime = {}; - FILETIME exitTime = {}; - FILETIME kernelTime = {}; - FILETIME userTime = {}; - if (::GetProcessTimes(hProcess, //__in HANDLE hProcess, - &creationTime, //__out LPFILETIME lpCreationTime, - &exitTime, //__out LPFILETIME lpExitTime, - &kernelTime, //__out LPFILETIME lpKernelTime, - &userTime)) //__out LPFILETIME lpUserTime - if (::CompareFileTime(&iter->Process.ProcessStartTime, &creationTime) == 0) - { - DWORD bufferSize = MAX_PATH; - std::vector<wchar_t> buffer(bufferSize); - if (::QueryFullProcessImageName(hProcess, //__in HANDLE hProcess, - 0, //__in DWORD dwFlags, - &buffer[0], //__out LPTSTR lpExeName, - &bufferSize)) //__inout PDWORD lpdwSize - if (bufferSize < buffer.size()) - processName += std::wstring(L", ") + L"\"" + &buffer[0] + L"\""; - } - } - output.push_back(processName); - } - return output; -} - - -boost::thread_specific_ptr<std::wstring> lastErrorMessage; //use "thread_local" in C++11 -} - - -bool fileop::moveToRecycleBin(const wchar_t* fileNames[], - size_t fileCount, - RecyclerCallback callback, - void* sink) -{ - try - { - ::moveToRecycleBin(fileNames, fileCount, callback, sink); //throw SysError - return true; - } - catch (const SysError& e) - { - lastErrorMessage.reset(new std::wstring(e.toString())); - return false; - } -} - - -bool fileop::copyFile(const wchar_t* sourceFile, - const wchar_t* targetFile) -{ - try - { - ::copyFile(sourceFile, targetFile); //throw SysError - return true; - } - catch (const SysError& e) - { - lastErrorMessage.reset(new std::wstring(e.toString())); - return false; - } -} - - -bool fileop::checkRecycler(const wchar_t* dirname, bool& isRecycler) -{ - try - { - CLSID clsid = {}; - getFolderClsid(dirname, clsid); //throw SysError - isRecycler = ::IsEqualCLSID(clsid, CLSID_RecycleBin) == TRUE; //silence perf warning - return true; - } - catch (const SysError& e) - { - lastErrorMessage.reset(new std::wstring(e.toString())); - return false; - } -} - - -const wchar_t* fileop::getLastError() -{ - return !lastErrorMessage.get() ? L"" : lastErrorMessage->c_str(); -} - - -bool fileop::getLockingProcesses(const wchar_t* filename, const wchar_t*& procList) -{ - try - { - std::vector<std::wstring> processes = ::getLockingProcesses(filename); //throw SysError - - std::wstring buffer; - std::for_each(processes.begin(), processes.end(), [&](const std::wstring& proc) { buffer += proc; buffer += L'\n'; }); - if (!processes.empty()) - buffer.resize(buffer.size() - 1); //remove last line break - - auto tmp = new wchar_t [buffer.size() + 1]; //bad_alloc ? - ::wmemcpy(tmp, buffer.c_str(), buffer.size() + 1); //include 0-termination - procList = tmp; //ownership passed - - return true; - } - catch (const SysError& e) - { - lastErrorMessage.reset(new std::wstring(e.toString())); - return false; - } -} - - -void fileop::freeString(const wchar_t* str) -{ - delete [] str; -} |