1 /*
2 Bacula(R) - The Network Backup Solution
3
4 Copyright (C) 2000-2020 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
volume_search(void * i1,void * i2)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
volume_cmp(void * e1,void * e2)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
getDriveType()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) */
isSuitableForSnapshot()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 */
search(char * p)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
addInSnapshotSet(char * p)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 */
get()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
create_vss_key()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 */
store_vssclient_in_tsd(VSSClient * cl)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
get_vssclient_from_tsd()272 static VSSClient *get_vssclient_from_tsd()
273 {
274 return (VSSClient *)pthread_getspecific(vssclient_key);
275 }
276
277 void
VSSCleanup(VSSClient * pVSSClient)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 */
VSSInit()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
VSSPathConverter()310 BOOL VSSPathConverter()
311 {
312 if (get_vssclient_from_tsd() == NULL) {
313 return false;
314 }
315 return true;
316 }
317
318 BOOL
VSSPathConvert(const char * szFilePath,char * szShadowPath,int nBuflen)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
VSSPathConvertW(const wchar_t * szFilePath,wchar_t * szShadowPath,int nBuflen)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
VSSClient()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
~VSSClient()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
InitializeForBackup(JCR * jcr)371 bool VSSClient::InitializeForBackup(JCR *jcr)
372 {
373 //return Initialize (VSS_CTX_BACKUP);
374 m_jcr = jcr;
375 return Initialize(0);
376 }
377
378
InitializeForRestore(JCR * jcr)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
AppendBackslash(wstring str)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
GetUniqueVolumeNameForPath(wstring path,wstring & rootPath)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
GetShadowPath(const char * szFilePath,char * szShadowPath,int nBuflen)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 */
GetShadowPathW(const wchar_t * szFilePath,wchar_t * szShadowPath,int nBuflen)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
GetWriterCount()545 const size_t VSSClient::GetWriterCount()
546 {
547 return m_pAlistWriterInfoText->size();
548 }
549
GetWriterInfo(int nIndex)550 const char* VSSClient::GetWriterInfo(int nIndex)
551 {
552 return (char*)m_pAlistWriterInfoText->get(nIndex);
553 }
554
555
GetWriterState(int nIndex)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
AppendWriterInfo(int nState,const char * pszInfo)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 */
DestroyWriterInfo()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
590