summaryrefslogtreecommitdiff
path: root/zen/IFileOperation/file_op.cpp
blob: 27a2565b67813fa4ddd3cba3631922a5df82baad (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
// **************************************************************************
// * 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;
}
bgstack15