]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/win32/filed/vss.cpp
Restore win32 dir from Branch-5.2 and update it
[bacula/bacula] / bacula / src / win32 / filed / vss.cpp
1 /*
2    Bacula(R) - The Network Backup Solution
3
4    Copyright (C) 2000-2018 Kern Sibbald
5
6    The original author of Bacula is Kern Sibbald, with contributions
7    from many others, a complete list can be found in the file AUTHORS.
8
9    You may use this file and others of this release according to the
10    license defined in the LICENSE file, which includes the Affero General
11    Public License, v3.0 ("AGPLv3") and some additional permissions and
12    terms pursuant to its AGPLv3 Section 7.
13
14    This notice must be preserved when any source code is
15    conveyed and/or propagated.
16
17    Bacula(R) is a registered trademark of Kern Sibbald.
18 */
19 // vss.cpp -- Interface to Volume Shadow Copies (VSS)
20 //
21 // Copyright transferred from MATRIX-Computer GmbH to
22 //   Kern Sibbald by express permission.
23 //
24 // Author          : Thorsten Engel
25 // Created On      : Fri May 06 21:44:00 2005
26
27
28 #ifdef WIN32_VSS
29 #include "bacula.h"
30 #include "compat.h"
31 #include "ms_atl.h"
32 #include <objbase.h>
33 #undef setlocale
34 #include <string>
35 using namespace std;
36
37 #include "vss.h"
38
39 #define dbglvl_snap DT_VOLUME|50
40
41 wstring GetUniqueVolumeNameForPath(wstring path, wstring &rootPath);
42
43 static int volume_search(void *i1, void *i2)
44 {
45    wstring   *volname = (wstring *) i1;
46    MTabEntry *vol = (MTabEntry *) i2;
47
48    return volname->compare(vol->volumeName);
49 }
50
51 static int volume_cmp(void *e1, void *e2)
52 {
53    MTabEntry *v1 = (MTabEntry *) e1;
54    MTabEntry *v2 = (MTabEntry *) e2;
55    return wcscmp(v1->volumeName, v2->volumeName);
56 }
57
58 UINT MTabEntry::getDriveType()
59 {
60    WCHAR *root = first();
61
62    // Make sure to discard CD-ROM and network drives
63    if (!root) {
64       return 0;
65    }
66
67    driveType = GetDriveTypeW(root);
68    return driveType;
69 }
70
71 /* Return true if the current volume can be snapshoted (ie not CDROM or fat32) */
72 bool MTabEntry::isSuitableForSnapshot()
73 {
74    DWORD componentlength, fsflags;
75    WCHAR fstype[50];
76    WCHAR *root = first();
77    UINT oldmode;
78    BOOL result;
79
80    // Make sure to discard CD-ROM and network drives
81    if (!root) {
82       Dmsg1(dbglvl_snap, "No mount point for %ls\n", volumeName);
83       goto bail_out;
84    }
85
86    if (getDriveType() != DRIVE_FIXED) {
87       Dmsg2(dbglvl_snap, "Invalid disk type %d for %ls\n", driveType, root);
88       goto bail_out;
89    }
90
91    /* From fstype.c, except that we have WCHAR instead of char */
92    /* We don't want any popups if there isn't any media in the drive */
93    oldmode = SetErrorMode(SEM_FAILCRITICALERRORS);
94    result = GetVolumeInformationW(root, NULL, 0, NULL,
95                                   &componentlength, &fsflags, fstype, ARRAYSIZE(fstype));
96    SetErrorMode(oldmode);
97
98    if (result) {
99       /* Windows returns NTFS, FAT, etc.  Make it lowercase to be consistent with other OSes */
100       Dmsg1(dbglvl_snap, "fstype=%ls\n", fstype);
101       if (!_wcsicmp(fstype, L"ntfs")) {
102          can_Snapshot = true;
103       }
104       if (!_wcsicmp(fstype, L"refs")) {
105          can_Snapshot = true;
106       }
107    }
108 bail_out:
109    Dmsg2(dbglvl_snap, "%ls is %s suitable for VSS snapshot\n", root, can_Snapshot?"":"not");
110    return can_Snapshot;
111 }
112
113 /* Find a volume for a specific path */
114 MTabEntry *MTab::search(char *p)
115 {
116    wstring volume;
117    wstring path;
118    wstring rootPath;
119
120    POOLMEM* pwszBuf = get_pool_memory(PM_FNAME);
121    UTF8_2_wchar(&pwszBuf, p);
122    path.assign((wchar_t *)pwszBuf);
123    volume = GetUniqueVolumeNameForPath(path, rootPath);
124
125    MTabEntry *elt = (MTabEntry *)entries->search(&volume, volume_search);
126    free_pool_memory(pwszBuf);
127
128    if (!elt) {
129       Dmsg1(000, "Unable to find %ls in volume list\n", volume.c_str());
130    }
131
132    return elt;
133 }
134
135 bool MTab::addInSnapshotSet(char *p)
136 {
137    MTabEntry *elt = search(p);
138    if (elt) {
139       if (!elt->in_SnapshotSet && elt->isSuitableForSnapshot()) {
140          nb_in_SnapshotSet++;
141          elt->setInSnapshotSet();
142       }
143    }
144    return nb_in_SnapshotSet == entries->size();
145 }
146
147 /* Initialize the "entries" list will all existing volumes */
148 bool MTab::get()
149 {
150    DWORD  count                = 0;
151    WCHAR  DeviceName[MAX_PATH] = L"";
152    HANDLE FindHandle           = INVALID_HANDLE_VALUE;
153    size_t Index                = 0;
154    bool   Success              = FALSE;
155    WCHAR  VolumeName[MAX_PATH] = L"";
156
157    Dmsg0(dbglvl_snap, "Filling MTAB\n");
158
159
160    //  Enumerate all volumes in the system.
161    FindHandle = FindFirstVolumeW(VolumeName, ARRAYSIZE(VolumeName));
162
163    if (FindHandle == INVALID_HANDLE_VALUE) {
164       lasterror = GetLastError();
165       return false;
166    }
167
168    for (;;) {
169       //  Skip the \\?\ prefix and remove the trailing backslash.
170       Index = wcslen(VolumeName) - 1;
171
172       if (VolumeName[0]     != L'\\' ||
173           VolumeName[1]     != L'\\' ||
174           VolumeName[2]     != L'?'  ||
175           VolumeName[3]     != L'\\' ||
176           VolumeName[Index] != L'\\')
177       {
178          lasterror = ERROR_BAD_PATHNAME;
179          lasterror_str = "FindFirstVolumeW/FindNextVolumeW returned a bad path";
180          Dmsg1(000, "FindFirstVolumeW/FindNextVolumeW returned a bad path %ls\n", VolumeName);
181          break;
182       }
183
184       //
185       //  QueryDosDeviceW does not allow a trailing backslash,
186       //  so temporarily remove it.
187       VolumeName[Index] = L'\0';
188
189       count = QueryDosDeviceW(&VolumeName[4], DeviceName,
190                               ARRAYSIZE(DeviceName));
191
192       VolumeName[Index] = L'\\';
193
194       if (count == 0) {
195          lasterror = GetLastError();
196          Dmsg1(000, "QueryDosDeviceW failed with error code %d\n", lasterror);
197          break;
198       }
199
200       MTabEntry *entry = New(MTabEntry(DeviceName, VolumeName));
201       entries->insert(entry, volume_cmp);
202
203       //
204       //  Move on to the next volume.
205       Success = FindNextVolumeW(FindHandle, VolumeName, ARRAYSIZE(VolumeName));
206
207       if (!Success) {
208          lasterror = GetLastError();
209          if (lasterror != ERROR_NO_MORE_FILES) {
210             Dmsg1(000, "FindNextVolumeW failed with error code %d\n", lasterror);
211             break;
212          }
213
214          //  Finished iterating
215          //  through all the volumes.
216          lasterror = ERROR_SUCCESS;
217          break;
218       }
219    }
220
221    FindVolumeClose(FindHandle);
222    FindHandle = INVALID_HANDLE_VALUE;
223
224    return true;
225 }
226
227 BOOL VSSPathConverter();
228 BOOL VSSPathConvert(const char *szFilePath, char *szShadowPath, int nBuflen);
229 BOOL VSSPathConvertW(const wchar_t *szFilePath, wchar_t *szShadowPath, int nBuflen);
230
231 // {b5946137-7b9f-4925-af80-51abd60b20d5}
232
233 static const GUID VSS_SWPRV_ProviderID =
234    { 0xb5946137, 0x7b9f, 0x4925, { 0xaf, 0x80, 0x51, 0xab, 0xd6, 0x0b, 0x20, 0xd5 } };
235
236 static pthread_once_t key_vss_once = PTHREAD_ONCE_INIT;
237 static pthread_key_t vssclient_key;
238
239 static void create_vss_key()
240 {
241    int status = pthread_key_create(&vssclient_key, NULL);
242    if (status != 0) {
243       berrno be;
244       Pmsg1(000, _("pthread key create failed: ERR=%s\n"),
245             be.bstrerror(status));
246       ASSERT2(0, "pthread_key_create failed");
247    }
248    SetVSSPathConvert(VSSPathConverter, VSSPathConvert, VSSPathConvertW);
249 }
250
251 /* TODO: Use the JCR variable to get the VSSClient pointer
252  * the JCR FileDaemon part is not known in the VSS library
253  */
254 static void store_vssclient_in_tsd(VSSClient *cl)
255 {
256    int status = pthread_once(&key_vss_once, create_vss_key);
257    if (status != 0) {
258       berrno be;
259       Pmsg1(000, _("pthread key create failed: ERR=%s\n"),
260             be.bstrerror(status));
261       ASSERT2(0, "pthread_once failed");
262    }
263
264    status = pthread_setspecific(vssclient_key, (void *)cl);
265    if (status != 0) {
266       berrno be;
267       Jmsg1(NULL, M_ABORT, 0, _("pthread_setspecific failed: ERR=%s\n"),
268             be.bstrerror(status));
269    }
270 }
271
272 static VSSClient *get_vssclient_from_tsd()
273 {
274    return (VSSClient *)pthread_getspecific(vssclient_key);
275 }
276
277 void
278 VSSCleanup(VSSClient *pVSSClient)
279 {
280    store_vssclient_in_tsd(NULL);
281    if (pVSSClient) {
282       delete (pVSSClient);
283    }
284 }
285
286 /*
287  * May be called multiple times
288  */
289 VSSClient *VSSInit()
290 {
291    VSSClient *pVSSClient = NULL;
292    /* decide which vss class to initialize */
293    if (g_MajorVersion == 5) {
294       switch (g_MinorVersion) {
295       case 1:
296          pVSSClient = new VSSClientXP();
297          break;
298       case 2:
299          pVSSClient = new VSSClient2003();
300          break;
301       }
302    /* Vista or Longhorn or later */
303    } else if (g_MajorVersion >= 6) {
304       pVSSClient = new VSSClientVista();
305    }
306    store_vssclient_in_tsd(pVSSClient);
307    return pVSSClient;
308 }
309
310 BOOL VSSPathConverter()
311 {
312    if (get_vssclient_from_tsd() == NULL) {
313       return false;
314    }
315    return true;
316 }
317
318 BOOL
319 VSSPathConvert(const char *szFilePath, char *szShadowPath, int nBuflen)
320 {
321    VSSClient *pVSSClient = get_vssclient_from_tsd();
322    if (pVSSClient) {
323       return pVSSClient->GetShadowPath(szFilePath, szShadowPath, nBuflen);
324    } else {
325       return false;
326    }
327 }
328
329 BOOL
330 VSSPathConvertW(const wchar_t *szFilePath, wchar_t *szShadowPath, int nBuflen)
331 {
332    VSSClient *pVSSClient = get_vssclient_from_tsd();
333    if (pVSSClient) {
334       return pVSSClient->GetShadowPathW(szFilePath, szShadowPath, nBuflen);
335    } else {
336       return false;
337    }
338 }
339
340 // Constructor
341 VSSClient::VSSClient()
342 {
343     memset(this, 0, sizeof(VSSClient));
344     m_pAlistWriterState = New(alist(10, not_owned_by_alist));
345     m_pAlistWriterInfoText = New(alist(10, owned_by_alist));
346     m_uidCurrentSnapshotSet = GUID_NULL;
347 }
348
349 // Destructor
350 VSSClient::~VSSClient()
351 {
352    // Release the IVssBackupComponents interface
353    // WARNING: this must be done BEFORE calling CoUninitialize()
354    if (m_pVssObject) {
355 //      m_pVssObject->Release();
356       m_pVssObject = NULL;
357    }
358
359    DestroyWriterInfo();
360    delete m_pAlistWriterState;
361    delete m_pAlistWriterInfoText;
362
363    // Call CoUninitialize if the CoInitialize was performed successfully
364    if (m_bCoInitializeCalled) {
365       CoUninitialize();
366    }
367
368    delete m_VolumeList;
369 }
370
371 bool VSSClient::InitializeForBackup(JCR *jcr)
372 {
373    //return Initialize (VSS_CTX_BACKUP);
374    m_jcr = jcr;
375    return Initialize(0);
376 }
377
378
379 bool VSSClient::InitializeForRestore(JCR *jcr)
380 {
381    m_metadata = NULL;
382    m_jcr = jcr;
383    return Initialize(0, true/*=>Restore*/);
384 }
385
386 // Append a backslash to the current string
387 wstring AppendBackslash(wstring str)
388 {
389     if (str.length() == 0) {
390         return wstring(L"\\");
391     }
392     if (str[str.length() - 1] == L'\\') {
393         return str;
394     }
395     return str.append(L"\\");
396 }
397
398 // Get the unique volume name for the given path
399 wstring GetUniqueVolumeNameForPath(wstring path, wstring &rootPath)
400 {
401     if (path.length() <= 0) {
402        //Dmsg0(50, "Failed path.len <= 0\n");
403        return L"";
404     }
405
406     // Add the backslash termination, if needed
407     path = AppendBackslash(path);
408     //Dmsg1(50, "Path=%ls\n", path.c_str());
409
410     // Get the root path of the volume
411     wchar_t volumeRootPath[MAX_PATH];
412     wchar_t volumeName[MAX_PATH];
413     wchar_t volumeUniqueName[MAX_PATH];
414
415     volumeRootPath[0] = 0;
416     volumeName[0] = 0;
417     volumeUniqueName[0] = 0;
418
419     if (!p_GetVolumePathNameW || !p_GetVolumePathNameW((LPCWSTR)path.c_str(), volumeRootPath, MAX_PATH)) {
420        Dmsg1(50, "Failed GetVolumePathNameW path=%ls\n", path.c_str());
421        return L"";
422     }
423     rootPath.assign(volumeRootPath);
424     Dmsg1(dbglvl_snap, "VolumeRootPath=%ls\n", volumeRootPath);
425
426     // Get the volume name alias (might be different from the unique volume name in rare cases)
427     if (!p_GetVolumeNameForVolumeMountPointW || !p_GetVolumeNameForVolumeMountPointW(volumeRootPath, volumeName, MAX_PATH)) {
428        Dmsg1(50, "Failed GetVolumeNameForVolumeMountPointW path=%ls\n", volumeRootPath);
429        return L"";
430     }
431     Dmsg1(dbglvl_snap, "VolumeName=%ls\n", volumeName);
432
433     // Get the unique volume name
434     if (!p_GetVolumeNameForVolumeMountPointW(volumeName, volumeUniqueName, MAX_PATH)) {
435        Dmsg1(50, "Failed GetVolumeNameForVolumeMountPointW path=%ls\n", volumeName);
436        return L"";
437     }
438     Dmsg1(dbglvl_snap, "VolumeUniqueName=%ls\n", volumeUniqueName);
439     return volumeUniqueName;
440 }
441
442 bool VSSClient::GetShadowPath(const char *szFilePath, char *szShadowPath, int nBuflen)
443 {
444    Dmsg1(dbglvl_snap, "GetShadowPath(%s)\n", szFilePath);
445
446    if (m_bDuringRestore) {
447       return false;
448    }
449
450    if (!m_bBackupIsInitialized) {
451       Jmsg0(m_jcr, M_FATAL, 0, "Backup is not Initialized\n");
452       return false;
453    }
454
455    wstring path, rootPath, volume;
456    POOLMEM* pwszBuf = get_pool_memory(PM_FNAME);
457
458    UTF8_2_wchar(&pwszBuf, szFilePath);
459    path.assign((wchar_t *)pwszBuf);
460
461    /* TODO: Have some cache here? */
462    volume = GetUniqueVolumeNameForPath(path, rootPath);
463
464    MTabEntry *vol = (MTabEntry *)m_VolumeList->entries->search(&volume,volume_search);
465    free_pool_memory(pwszBuf);
466
467    if (vol && vol->shadowCopyName) {
468       if (WideCharToMultiByte(CP_UTF8,0,vol->shadowCopyName,-1,szShadowPath,nBuflen-1,NULL,NULL)) {
469          nBuflen -= (int)strlen(szShadowPath);
470
471          bstrncat(szShadowPath, "\\", nBuflen);
472          nBuflen -= 1;
473         //Dmsg4(200,"szFilePath=%s rootPath=%ls len(rootPath)=%d nBuflen=%d\n",
474         //      szFilePath, rootPath.c_str(), rootPath.length(), nBuflen);
475
476          /* here we skip C:, we skip volume root */
477          /* TODO: I'm not 100% sure that rootPath.lenght() WCHAR means X CHAR 
478           * The main goal here is to convert
479           * c:/tmp/mounted/test -> \\?\Device\HardDiskSnapshot10\test
480           *
481           * So, we skip c:/tmp/mounted/ from the base file.
482           */
483          if (strlen(szFilePath) > rootPath.length()) {
484             bstrncat(szShadowPath, szFilePath+rootPath.length(), nBuflen);
485          }
486          Dmsg2(dbglvl_snap, "GetShadowPath(%s) -> %s\n", szFilePath, szShadowPath);
487          return true;
488       }
489    }
490
491    bstrncpy(szShadowPath, szFilePath, nBuflen);
492    Dmsg2(dbglvl_snap, "GetShadowPath(%s) -> %s\n", szFilePath, szShadowPath);
493    errno = EINVAL;
494    return false;
495 }
496
497 /*
498  * c:/tmp   ->   \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy15\tmp
499  */
500 bool VSSClient::GetShadowPathW(const wchar_t *szFilePath, wchar_t *szShadowPath, int nBuflen)
501 {
502    Dmsg1(dbglvl_snap, "GetShadowPathW(%ls)\n", szFilePath);
503
504    if (m_bDuringRestore) {
505       return false;
506    }
507
508    if (!m_bBackupIsInitialized) {
509       Jmsg0(m_jcr, M_FATAL, 0, "Backup is not Initialized\n");
510       return false;
511    }
512    wstring path, rootPath, volume;
513    path.assign((wchar_t *)szFilePath);
514    /* TODO: Have some cache here? */
515    volume = GetUniqueVolumeNameForPath(path, rootPath);
516    MTabEntry *vol = (MTabEntry *)m_VolumeList->entries->search(&volume,volume_search);
517
518    if (vol && vol->shadowCopyName) {
519       Dmsg5(dbglvl_snap, "szFilePath=%ls rootPath=%ls len(rootPath)=%d nBuflen=%d shadowCopyName=%ls\n",
520             szFilePath, rootPath.c_str(), rootPath.length(), nBuflen, vol->shadowCopyName);
521
522       wcsncpy(szShadowPath, vol->shadowCopyName, nBuflen);
523       nBuflen -= (int)wcslen(vol->shadowCopyName);
524
525       wcsncat(szShadowPath, L"\\", nBuflen);
526       nBuflen -= 1;
527
528       //Dmsg4(200, "szFilePath=%ls rootPath=%ls len(rootPath)=%d nBuflen=%d\n",
529       //      szFilePath, rootPath.c_str(), rootPath.length(), nBuflen);
530
531       if (wcslen(szFilePath) > rootPath.length()) {
532          /* here we skip C:, we skip volume root */
533          wcsncat(szShadowPath, szFilePath+rootPath.length(), nBuflen);
534       }
535       Dmsg2(dbglvl_snap, "GetShadowPathW(%ls) -> %ls\n", szFilePath, szShadowPath);
536       return true;
537    }
538
539    wcsncpy(szShadowPath, szFilePath, nBuflen);
540    Dmsg2(dbglvl_snap, "GetShadowPathW(%ls) -> %ls\n", szFilePath, szShadowPath);
541    errno = EINVAL;
542    return false;
543 }
544
545 const size_t VSSClient::GetWriterCount()
546 {
547    return m_pAlistWriterInfoText->size();
548 }
549
550 const char* VSSClient::GetWriterInfo(int nIndex)
551 {
552    return (char*)m_pAlistWriterInfoText->get(nIndex);
553 }
554
555
556 const int VSSClient::GetWriterState(int nIndex)
557 {
558    void *item = m_pAlistWriterState->get(nIndex);
559
560 /* Eliminate compiler warnings */
561 #ifdef HAVE_VSS64
562    return (int64_t)(char *)item;
563 #else
564    return (int)(char *)item;
565 #endif
566 }
567
568 void VSSClient::AppendWriterInfo(int nState, const char* pszInfo)
569 {
570    m_pAlistWriterInfoText->push(bstrdup(pszInfo));
571    m_pAlistWriterState->push((void*)(intptr_t)nState);
572 }
573
574 /*
575  * Note, this is called at the end of every job, so release all
576  *  the items in the alists, but do not delete the alist.
577  */
578 void VSSClient::DestroyWriterInfo()
579 {
580    while (!m_pAlistWriterInfoText->empty()) {
581       free(m_pAlistWriterInfoText->pop());
582    }
583
584    while (!m_pAlistWriterState->empty()) {
585       m_pAlistWriterState->pop();
586    }
587 }
588
589 #endif