summaryrefslogtreecommitdiff
path: root/lib/shadow.cpp
blob: d1027f691f72d285a635a81000129e818d28453d (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
// **************************************************************************
// * 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 "shadow.h"
#include <stdexcept>
#include <zen/win.h> //includes "windows.h"
#include <zen/dll.h>
#include <zen/win_ver.h>
#include <zen/assert_static.h>
#include <zen/long_path_prefix.h>
#include <zen/symlink_target.h>
#include "ShadowCopy/shadow.h"
#include <zen/scope_guard.h>

using namespace zen;
using namespace shadow;


namespace
{
bool runningWOW64() //test if process is running under WOW64 (reference http://msdn.microsoft.com/en-us/library/ms684139(VS.85).aspx)
{
    typedef BOOL (WINAPI* IsWow64ProcessFun)(HANDLE hProcess, PBOOL Wow64Process);

    const SysDllFun<IsWow64ProcessFun> isWow64Process(L"kernel32.dll", "IsWow64Process");
    if (isWow64Process)
    {
        BOOL isWow64 = FALSE;
        if (isWow64Process(::GetCurrentProcess(), &isWow64))
            return isWow64 != FALSE;
    }
    return false;
}

const bool wereVistaOrLater = vistaOrLater(); //thread-safety: init at startup
}

//#############################################################################################################

class ShadowCopy::ShadowVolume
{
public:
    ShadowVolume(const Zstring& volumeNamePf) : //throw FileError
        createShadowCopy (getDllName(), funName_createShadowCopy),
        releaseShadowCopy(getDllName(), funName_releaseShadowCopy),
        getShadowVolume  (getDllName(), funName_getShadowVolume),
        getLastError     (getDllName(), funName_getLastError),
        backupHandle(nullptr)
    {
        //VSS does not support running under WOW64 except for Windows XP and Windows Server 2003
        //reference: http://msdn.microsoft.com/en-us/library/aa384627(VS.85).aspx
        if (runningWOW64())
            throw FileError(_("Cannot access the Volume Shadow Copy Service."),
                            _("Please use FreeFileSync 64-bit version to create shadow copies on this system."));

        //check if shadow copy dll was loaded correctly
        if (!createShadowCopy || !releaseShadowCopy || !getShadowVolume || !getLastError)
            throw FileError(_("Cannot access the Volume Shadow Copy Service."),
                            replaceCpy(_("Cannot load file %x."), L"%x", fmtFileName(getDllName())));

        //---------------------------------------------------------------------------------------------------------
        //start volume shadow copy service:
        backupHandle = createShadowCopy(volumeNamePf.c_str());
        if (!backupHandle)
            throw FileError(_("Cannot access the Volume Shadow Copy Service."),
                            getLastError() + std::wstring(L" Volume: ") + fmtFileName(volumeNamePf));

        shadowVolPf = appendSeparator(getShadowVolume(backupHandle)); //shadowVolName NEVER has a trailing backslash
    }

    ~ShadowVolume() { releaseShadowCopy(backupHandle); } //fast! no performance optimization necessary

    Zstring geNamePf() const { return shadowVolPf; } //with trailing path separator

private:
    ShadowVolume(const ShadowVolume&);
    ShadowVolume& operator=(const ShadowVolume&);

    const DllFun<FunType_createShadowCopy>  createShadowCopy;
    const DllFun<FunType_releaseShadowCopy> releaseShadowCopy;
    const DllFun<FunType_getShadowVolume>   getShadowVolume;
    const DllFun<FunType_getLastError>      getLastError;

    Zstring shadowVolPf;
    ShadowHandle backupHandle;
};

//#############################################################################################################

Zstring ShadowCopy::makeShadowCopy(const Zstring& inputFile, const std::function<void(const Zstring&)>& onBeforeMakeVolumeCopy)
{
    Zstring filenameFinal = inputFile;

    //try to resolve symlinks and junctions:
    //1. symlinks: we need to retrieve the target path, else we would just return a symlink on a VSS volume while the target outside were still locked!
    //2. junctions: C:\Users\<username> is a junction that may link to e.g. D:\Users\<username>, so GetVolumePathName() returns "D:\" => "Volume name %x not part of file name %y."
    if (wereVistaOrLater)
        filenameFinal = getResolvedFilePath(inputFile); //throw FileError; requires Vista or later!
    //-> returns paths with \\?\ prefix! => make sure to avoid duplicate shadow copies for volume paths with/without prefix

    DWORD bufferSize = 10000;
    std::vector<wchar_t> volBuffer(bufferSize);
    if (!::GetVolumePathName(filenameFinal.c_str(), //__in   LPCTSTR lpszFileName,
                             &volBuffer[0],         //__out  LPTSTR lpszVolumePathName,
                             bufferSize))           //__in   DWORD cchBufferLength
        throw FileError(replaceCpy(_("Cannot determine volume name for %x."), L"%x", fmtFileName(filenameFinal)), formatSystemError(L"GetVolumePathName", ::GetLastError()));

    const Zstring volumeNamePf = appendSeparator(&volBuffer[0]); //msdn: if buffer is 1 char too short, GetVolumePathName() may skip last separator without error!

    //input file is always absolute! directory formatting takes care of this! Therefore volume name can always be found.
    const size_t pos = filenameFinal.find(volumeNamePf); //filenameFinal needs NOT to begin with volumeNamePf: consider \\?\ prefix!
    if (pos == Zstring::npos)
        throw FileError(replaceCpy(replaceCpy(_("Volume name %x is not part of file path %y."),
                                              L"%x", fmtFileName(volumeNamePf)),
                                   L"%y", fmtFileName(filenameFinal)));

    //get or create instance of shadow volume
    auto it = shadowVol.find(volumeNamePf);
    if (it == shadowVol.end())
    {
        onBeforeMakeVolumeCopy(volumeNamePf); //notify client before (potentially) blocking some time
        auto newEntry = std::make_shared<ShadowVolume>(volumeNamePf); //throw FileError
        it = shadowVol.insert(std::make_pair(volumeNamePf, newEntry)).first;
    }

    //return filename alias on shadow copy volume
    return it->second->geNamePf() + Zstring(filenameFinal.c_str() + pos + volumeNamePf.length());
}
bgstack15