2 Bacula(R) - The Network Backup Solution
4 Copyright (C) 2000-2018 Kern Sibbald
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.
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.
14 This notice must be preserved when any source code is
15 conveyed and/or propagated.
17 Bacula(R) is a registered trademark of Kern Sibbald.
19 // vss.cpp -- Interface to Volume Shadow Copies (VSS)
21 // Copyright transferred from MATRIX-Computer GmbH to
22 // Kern Sibbald by express permission.
24 // Author : Thorsten Engel
25 // Created On : Fri May 06 21:44:00 2005
39 #define dbglvl_snap DT_VOLUME|50
41 wstring GetUniqueVolumeNameForPath(wstring path, wstring &rootPath);
43 static int volume_search(void *i1, void *i2)
45 wstring *volname = (wstring *) i1;
46 MTabEntry *vol = (MTabEntry *) i2;
48 return volname->compare(vol->volumeName);
51 static int volume_cmp(void *e1, void *e2)
53 MTabEntry *v1 = (MTabEntry *) e1;
54 MTabEntry *v2 = (MTabEntry *) e2;
55 return wcscmp(v1->volumeName, v2->volumeName);
58 UINT MTabEntry::getDriveType()
60 WCHAR *root = first();
62 // Make sure to discard CD-ROM and network drives
67 driveType = GetDriveTypeW(root);
71 /* Return true if the current volume can be snapshoted (ie not CDROM or fat32) */
72 bool MTabEntry::isSuitableForSnapshot()
74 DWORD componentlength, fsflags;
76 WCHAR *root = first();
80 // Make sure to discard CD-ROM and network drives
82 Dmsg1(dbglvl_snap, "No mount point for %ls\n", volumeName);
86 if (getDriveType() != DRIVE_FIXED) {
87 Dmsg2(dbglvl_snap, "Invalid disk type %d for %ls\n", driveType, root);
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);
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")) {
104 if (!_wcsicmp(fstype, L"refs")) {
109 Dmsg2(dbglvl_snap, "%ls is %s suitable for VSS snapshot\n", root, can_Snapshot?"":"not");
113 /* Find a volume for a specific path */
114 MTabEntry *MTab::search(char *p)
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);
125 MTabEntry *elt = (MTabEntry *)entries->search(&volume, volume_search);
126 free_pool_memory(pwszBuf);
129 Dmsg1(000, "Unable to find %ls in volume list\n", volume.c_str());
135 bool MTab::addInSnapshotSet(char *p)
137 MTabEntry *elt = search(p);
139 if (!elt->in_SnapshotSet && elt->isSuitableForSnapshot()) {
141 elt->setInSnapshotSet();
144 return nb_in_SnapshotSet == entries->size();
147 /* Initialize the "entries" list will all existing volumes */
151 WCHAR DeviceName[MAX_PATH] = L"";
152 HANDLE FindHandle = INVALID_HANDLE_VALUE;
154 bool Success = FALSE;
155 WCHAR VolumeName[MAX_PATH] = L"";
157 Dmsg0(dbglvl_snap, "Filling MTAB\n");
160 // Enumerate all volumes in the system.
161 FindHandle = FindFirstVolumeW(VolumeName, ARRAYSIZE(VolumeName));
163 if (FindHandle == INVALID_HANDLE_VALUE) {
164 lasterror = GetLastError();
169 // Skip the \\?\ prefix and remove the trailing backslash.
170 Index = wcslen(VolumeName) - 1;
172 if (VolumeName[0] != L'\\' ||
173 VolumeName[1] != L'\\' ||
174 VolumeName[2] != L'?' ||
175 VolumeName[3] != L'\\' ||
176 VolumeName[Index] != L'\\')
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);
185 // QueryDosDeviceW does not allow a trailing backslash,
186 // so temporarily remove it.
187 VolumeName[Index] = L'\0';
189 count = QueryDosDeviceW(&VolumeName[4], DeviceName,
190 ARRAYSIZE(DeviceName));
192 VolumeName[Index] = L'\\';
195 lasterror = GetLastError();
196 Dmsg1(000, "QueryDosDeviceW failed with error code %d\n", lasterror);
200 MTabEntry *entry = New(MTabEntry(DeviceName, VolumeName));
201 entries->insert(entry, volume_cmp);
204 // Move on to the next volume.
205 Success = FindNextVolumeW(FindHandle, VolumeName, ARRAYSIZE(VolumeName));
208 lasterror = GetLastError();
209 if (lasterror != ERROR_NO_MORE_FILES) {
210 Dmsg1(000, "FindNextVolumeW failed with error code %d\n", lasterror);
214 // Finished iterating
215 // through all the volumes.
216 lasterror = ERROR_SUCCESS;
221 FindVolumeClose(FindHandle);
222 FindHandle = INVALID_HANDLE_VALUE;
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);
231 // {b5946137-7b9f-4925-af80-51abd60b20d5}
233 static const GUID VSS_SWPRV_ProviderID =
234 { 0xb5946137, 0x7b9f, 0x4925, { 0xaf, 0x80, 0x51, 0xab, 0xd6, 0x0b, 0x20, 0xd5 } };
236 static pthread_once_t key_vss_once = PTHREAD_ONCE_INIT;
237 static pthread_key_t vssclient_key;
239 static void create_vss_key()
241 int status = pthread_key_create(&vssclient_key, NULL);
244 Pmsg1(000, _("pthread key create failed: ERR=%s\n"),
245 be.bstrerror(status));
246 ASSERT2(0, "pthread_key_create failed");
248 SetVSSPathConvert(VSSPathConverter, VSSPathConvert, VSSPathConvertW);
251 /* TODO: Use the JCR variable to get the VSSClient pointer
252 * the JCR FileDaemon part is not known in the VSS library
254 static void store_vssclient_in_tsd(VSSClient *cl)
256 int status = pthread_once(&key_vss_once, create_vss_key);
259 Pmsg1(000, _("pthread key create failed: ERR=%s\n"),
260 be.bstrerror(status));
261 ASSERT2(0, "pthread_once failed");
264 status = pthread_setspecific(vssclient_key, (void *)cl);
267 Jmsg1(NULL, M_ABORT, 0, _("pthread_setspecific failed: ERR=%s\n"),
268 be.bstrerror(status));
272 static VSSClient *get_vssclient_from_tsd()
274 return (VSSClient *)pthread_getspecific(vssclient_key);
278 VSSCleanup(VSSClient *pVSSClient)
280 store_vssclient_in_tsd(NULL);
287 * May be called multiple times
291 VSSClient *pVSSClient = NULL;
292 /* decide which vss class to initialize */
293 if (g_MajorVersion == 5) {
294 switch (g_MinorVersion) {
296 pVSSClient = new VSSClientXP();
299 pVSSClient = new VSSClient2003();
302 /* Vista or Longhorn or later */
303 } else if (g_MajorVersion >= 6) {
304 pVSSClient = new VSSClientVista();
306 store_vssclient_in_tsd(pVSSClient);
310 BOOL VSSPathConverter()
312 if (get_vssclient_from_tsd() == NULL) {
319 VSSPathConvert(const char *szFilePath, char *szShadowPath, int nBuflen)
321 VSSClient *pVSSClient = get_vssclient_from_tsd();
323 return pVSSClient->GetShadowPath(szFilePath, szShadowPath, nBuflen);
330 VSSPathConvertW(const wchar_t *szFilePath, wchar_t *szShadowPath, int nBuflen)
332 VSSClient *pVSSClient = get_vssclient_from_tsd();
334 return pVSSClient->GetShadowPathW(szFilePath, szShadowPath, nBuflen);
341 VSSClient::VSSClient()
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;
350 VSSClient::~VSSClient()
352 // Release the IVssBackupComponents interface
353 // WARNING: this must be done BEFORE calling CoUninitialize()
355 // m_pVssObject->Release();
360 delete m_pAlistWriterState;
361 delete m_pAlistWriterInfoText;
363 // Call CoUninitialize if the CoInitialize was performed successfully
364 if (m_bCoInitializeCalled) {
371 bool VSSClient::InitializeForBackup(JCR *jcr)
373 //return Initialize (VSS_CTX_BACKUP);
375 return Initialize(0);
379 bool VSSClient::InitializeForRestore(JCR *jcr)
383 return Initialize(0, true/*=>Restore*/);
386 // Append a backslash to the current string
387 wstring AppendBackslash(wstring str)
389 if (str.length() == 0) {
390 return wstring(L"\\");
392 if (str[str.length() - 1] == L'\\') {
395 return str.append(L"\\");
398 // Get the unique volume name for the given path
399 wstring GetUniqueVolumeNameForPath(wstring path, wstring &rootPath)
401 if (path.length() <= 0) {
402 //Dmsg0(50, "Failed path.len <= 0\n");
406 // Add the backslash termination, if needed
407 path = AppendBackslash(path);
408 //Dmsg1(50, "Path=%ls\n", path.c_str());
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];
415 volumeRootPath[0] = 0;
417 volumeUniqueName[0] = 0;
419 if (!p_GetVolumePathNameW || !p_GetVolumePathNameW((LPCWSTR)path.c_str(), volumeRootPath, MAX_PATH)) {
420 Dmsg1(50, "Failed GetVolumePathNameW path=%ls\n", path.c_str());
423 rootPath.assign(volumeRootPath);
424 Dmsg1(dbglvl_snap, "VolumeRootPath=%ls\n", volumeRootPath);
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);
431 Dmsg1(dbglvl_snap, "VolumeName=%ls\n", volumeName);
433 // Get the unique volume name
434 if (!p_GetVolumeNameForVolumeMountPointW(volumeName, volumeUniqueName, MAX_PATH)) {
435 Dmsg1(50, "Failed GetVolumeNameForVolumeMountPointW path=%ls\n", volumeName);
438 Dmsg1(dbglvl_snap, "VolumeUniqueName=%ls\n", volumeUniqueName);
439 return volumeUniqueName;
442 bool VSSClient::GetShadowPath(const char *szFilePath, char *szShadowPath, int nBuflen)
444 Dmsg1(dbglvl_snap, "GetShadowPath(%s)\n", szFilePath);
446 if (m_bDuringRestore) {
450 if (!m_bBackupIsInitialized) {
451 Jmsg0(m_jcr, M_FATAL, 0, "Backup is not Initialized\n");
455 wstring path, rootPath, volume;
456 POOLMEM* pwszBuf = get_pool_memory(PM_FNAME);
458 UTF8_2_wchar(&pwszBuf, szFilePath);
459 path.assign((wchar_t *)pwszBuf);
461 /* TODO: Have some cache here? */
462 volume = GetUniqueVolumeNameForPath(path, rootPath);
464 MTabEntry *vol = (MTabEntry *)m_VolumeList->entries->search(&volume,volume_search);
465 free_pool_memory(pwszBuf);
467 if (vol && vol->shadowCopyName) {
468 if (WideCharToMultiByte(CP_UTF8,0,vol->shadowCopyName,-1,szShadowPath,nBuflen-1,NULL,NULL)) {
469 nBuflen -= (int)strlen(szShadowPath);
471 bstrncat(szShadowPath, "\\", nBuflen);
473 //Dmsg4(200,"szFilePath=%s rootPath=%ls len(rootPath)=%d nBuflen=%d\n",
474 // szFilePath, rootPath.c_str(), rootPath.length(), nBuflen);
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
481 * So, we skip c:/tmp/mounted/ from the base file.
483 if (strlen(szFilePath) > rootPath.length()) {
484 bstrncat(szShadowPath, szFilePath+rootPath.length(), nBuflen);
486 Dmsg2(dbglvl_snap, "GetShadowPath(%s) -> %s\n", szFilePath, szShadowPath);
491 bstrncpy(szShadowPath, szFilePath, nBuflen);
492 Dmsg2(dbglvl_snap, "GetShadowPath(%s) -> %s\n", szFilePath, szShadowPath);
498 * c:/tmp -> \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy15\tmp
500 bool VSSClient::GetShadowPathW(const wchar_t *szFilePath, wchar_t *szShadowPath, int nBuflen)
502 Dmsg1(dbglvl_snap, "GetShadowPathW(%ls)\n", szFilePath);
504 if (m_bDuringRestore) {
508 if (!m_bBackupIsInitialized) {
509 Jmsg0(m_jcr, M_FATAL, 0, "Backup is not Initialized\n");
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);
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);
522 wcsncpy(szShadowPath, vol->shadowCopyName, nBuflen);
523 nBuflen -= (int)wcslen(vol->shadowCopyName);
525 wcsncat(szShadowPath, L"\\", nBuflen);
528 //Dmsg4(200, "szFilePath=%ls rootPath=%ls len(rootPath)=%d nBuflen=%d\n",
529 // szFilePath, rootPath.c_str(), rootPath.length(), nBuflen);
531 if (wcslen(szFilePath) > rootPath.length()) {
532 /* here we skip C:, we skip volume root */
533 wcsncat(szShadowPath, szFilePath+rootPath.length(), nBuflen);
535 Dmsg2(dbglvl_snap, "GetShadowPathW(%ls) -> %ls\n", szFilePath, szShadowPath);
539 wcsncpy(szShadowPath, szFilePath, nBuflen);
540 Dmsg2(dbglvl_snap, "GetShadowPathW(%ls) -> %ls\n", szFilePath, szShadowPath);
545 const size_t VSSClient::GetWriterCount()
547 return m_pAlistWriterInfoText->size();
550 const char* VSSClient::GetWriterInfo(int nIndex)
552 return (char*)m_pAlistWriterInfoText->get(nIndex);
556 const int VSSClient::GetWriterState(int nIndex)
558 void *item = m_pAlistWriterState->get(nIndex);
560 /* Eliminate compiler warnings */
562 return (int64_t)(char *)item;
564 return (int)(char *)item;
568 void VSSClient::AppendWriterInfo(int nState, const char* pszInfo)
570 m_pAlistWriterInfoText->push(bstrdup(pszInfo));
571 m_pAlistWriterState->push((void*)(intptr_t)nState);
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.
578 void VSSClient::DestroyWriterInfo()
580 while (!m_pAlistWriterInfoText->empty()) {
581 free(m_pAlistWriterInfoText->pop());
584 while (!m_pAlistWriterState->empty()) {
585 m_pAlistWriterState->pop();