1 /*
2    Bacula® - The Network Backup Solution
3 
4    Copyright (C) 2005-2008 Free Software Foundation Europe e.V.
5 
6    The main author of Bacula is Kern Sibbald, with contributions from
7    many others, a complete list can be found in the file AUTHORS.
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version three of the GNU Affero General Public
10    License as published by the Free Software Foundation and included
11    in the file LICENSE.
12 
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    General Public License for more details.
17 
18    You should have received a copy of the GNU Affero General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22 
23    Bacula® is a registered trademark of Kern Sibbald.
24    The licensor of Bacula is the Free Software Foundation Europe
25    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26    Switzerland, email:ftf@fsfeurope.org.
27 */
28 //                              -*- Mode: C++ -*-
29 // vss.cpp -- Interface to Volume Shadow Copies (VSS)
30 //
31 // Copyright transferred from MATRIX-Computer GmbH to
32 //   Kern Sibbald by express permission.
33 //
34 // Author          : Thorsten Engel
35 // Created On      : Fri May 06 21:44:00 2005
36 
37 
38 #ifdef WIN32_VSS
39 
40 #include "burp.h"
41 #include "berrno.h"
42 #include "../../log.h"
43 
44 #undef setlocale
45 
46 // STL includes.
47 #include <vector>
48 #include <algorithm>
49 #include <string>
50 #include <sstream>
51 #include <fstream>
52 using namespace std;
53 
54 #include "ms_atl.h"
55 #include <objbase.h>
56 
57 /* Kludges to get Vista code to compile.
58    KES - June 2007 */
59 #define __in  IN
60 #define __out OUT
61 #define __RPC_unique_pointer
62 #define __RPC_string
63 #define __RPC__deref_inout_opt
64 #define __RPC__out
65 
66 #ifndef ENABLE_NLS
67 	#define setlocale(p, d)
68 #endif
69 
70 #ifdef HAVE_STRSAFE_H
71 	// Used for safe string manipulation
72 	#include <strsafe.h>
73 #endif
74 
75 BOOL VSSPathConvert(const char *szFilePath,
76 	char *szShadowPath, int nBuflen);
77 BOOL VSSPathConvertW(const wchar_t *szFilePath,
78 	wchar_t *szShadowPath, int nBuflen);
79 
80 class IXMLDOMDocument;
81 
82 // Reduce compiler warnings from Windows vss code.
83 #define uuid(x)
84 
85 #ifdef B_VSS_XP
86 	#define VSSClientGeneric VSSClientXP
87 	#include "inc/winxp/vss.h"
88 	#include "inc/winxp/vswriter.h"
89 	#include "inc/winxp/vsbackup.h"
90 #endif
91 
92 #ifdef B_VSS_W2K3
93 	#define VSSClientGeneric VSSClient2003
94 	#include "inc/win2003/vss.h"
95 	#include "inc/win2003/vswriter.h"
96 	#include "inc/win2003/vsbackup.h"
97 #endif
98 
99 #ifdef B_VSS_VISTA
100 	#define VSSClientGeneric VSSClientVista
101 	#include "inc/win2003/vss.h"
102 	#include "inc/win2003/vswriter.h"
103 	#include "inc/win2003/vsbackup.h"
104 #endif
105 
106 // In VSSAPI.DLL.
107 typedef HRESULT (STDAPICALLTYPE* t_CreateVssBackupComponents)
108 	(OUT IVssBackupComponents **);
109 typedef void (APIENTRY* t_VssFreeSnapshotProperties)
110 	(IN VSS_SNAPSHOT_PROP*);
111 
112 static t_CreateVssBackupComponents p_CreateVssBackupComponents=NULL;
113 static t_VssFreeSnapshotProperties p_VssFreeSnapshotProperties=NULL;
114 
115 #include "vss.h"
116 
117 // Some helper functions.
118 
AppendBackslash(wstring str)119 inline wstring AppendBackslash(wstring str)
120 {
121 	if(!str.length())
122 		return wstring(L"\\");
123 	if(str[str.length() - 1]==L'\\')
124 		return str;
125 	return str.append(L"\\");
126 }
127 
128 // Get the unique volume name for the given path.
GetUniqueVolumeNameForPath(wstring path)129 inline wstring GetUniqueVolumeNameForPath(wstring path)
130 {
131 	if(path.length()<=0) return L"";
132 
133 	// Add the backslash termination, if needed
134 	path=AppendBackslash(path);
135 
136 	// Get the root path of the volume
137 	wchar_t volumeRootPath[MAX_PATH];
138 	wchar_t volumeName[MAX_PATH];
139 	wchar_t volumeUniqueName[MAX_PATH];
140 
141 	if(!p_GetVolumePathNameW
142 	  || !p_GetVolumePathNameW((LPCWSTR)path.c_str(),
143 		volumeRootPath, MAX_PATH))
144 			return L"";
145 
146 	// Get the volume name alias (might be different from the unique volume
147 	// name in rare cases).
148 	if(!p_GetVolumeNameForVolumeMountPointW
149 	  || !p_GetVolumeNameForVolumeMountPointW(volumeRootPath,
150 		volumeName, MAX_PATH))
151 			return L"";
152 
153 	// Get the unique volume name.
154 	if(!p_GetVolumeNameForVolumeMountPointW(volumeName,
155 		volumeUniqueName, MAX_PATH))
156 			return L"";
157 
158 	return volumeUniqueName;
159 }
160 
161 
162 // Helper macro for quick treatment of case statements for error codes.
163 #define GEN_MERGE(A, B) A##B
164 #define GEN_MAKE_W(A) GEN_MERGE(L, A)
165 
166 #define CHECK_CASE_FOR_CONSTANT(value) case value: return (GEN_MAKE_W(#value));
167 
168 
169 // Convert a writer status into a string.
GetStringFromWriterStatus(VSS_WRITER_STATE eWriterStatus)170 inline const wchar_t* GetStringFromWriterStatus(VSS_WRITER_STATE eWriterStatus)
171 {
172 	switch(eWriterStatus)
173 	{
174 		CHECK_CASE_FOR_CONSTANT(VSS_WS_STABLE);
175 		CHECK_CASE_FOR_CONSTANT(VSS_WS_WAITING_FOR_FREEZE);
176 		CHECK_CASE_FOR_CONSTANT(VSS_WS_WAITING_FOR_THAW);
177 		CHECK_CASE_FOR_CONSTANT(VSS_WS_WAITING_FOR_POST_SNAPSHOT);
178 		CHECK_CASE_FOR_CONSTANT(VSS_WS_WAITING_FOR_BACKUP_COMPLETE);
179 		CHECK_CASE_FOR_CONSTANT(VSS_WS_FAILED_AT_IDENTIFY);
180 		CHECK_CASE_FOR_CONSTANT(VSS_WS_FAILED_AT_PREPARE_BACKUP);
181 		CHECK_CASE_FOR_CONSTANT(VSS_WS_FAILED_AT_PREPARE_SNAPSHOT);
182 		CHECK_CASE_FOR_CONSTANT(VSS_WS_FAILED_AT_FREEZE);
183 		CHECK_CASE_FOR_CONSTANT(VSS_WS_FAILED_AT_THAW);
184 		CHECK_CASE_FOR_CONSTANT(VSS_WS_FAILED_AT_POST_SNAPSHOT);
185 		CHECK_CASE_FOR_CONSTANT(VSS_WS_FAILED_AT_BACKUP_COMPLETE);
186 		CHECK_CASE_FOR_CONSTANT(VSS_WS_FAILED_AT_PRE_RESTORE);
187 		CHECK_CASE_FOR_CONSTANT(VSS_WS_FAILED_AT_POST_RESTORE);
188 
189 		default:
190 			return L"Error or Undefined";
191 	}
192 }
193 
194 #ifdef HAVE_VSS64
195 	// 64 bit entrypoint name.
196 	#define VSSVBACK_ENTRY \
197 	  "?CreateVssBackupComponents@@YAJPEAPEAVIVssBackupComponents@@@Z"
198 #else
199 	// 32 bit entrypoint name.
200 	#define VSSVBACK_ENTRY \
201 	  "?CreateVssBackupComponents@@YGJPAPAVIVssBackupComponents@@@Z"
202 #endif
203 
VSSClientGeneric()204 VSSClientGeneric::VSSClientGeneric()
205 {
206 	m_hLib=LoadLibraryA("VSSAPI.DLL");
207 	if(!m_hLib) return;
208 	p_CreateVssBackupComponents=(t_CreateVssBackupComponents)
209 		GetProcAddress(m_hLib, VSSVBACK_ENTRY);
210 	p_VssFreeSnapshotProperties=(t_VssFreeSnapshotProperties)
211 		GetProcAddress(m_hLib, "VssFreeSnapshotProperties");
212 }
213 
~VSSClientGeneric()214 VSSClientGeneric::~VSSClientGeneric()
215 {
216 	if(m_hLib) FreeLibrary(m_hLib);
217 }
218 
bsystem_error(void)219 static BOOL bsystem_error(void)
220 {
221 	errno=ENOSYS;
222 	return FALSE;
223 }
224 
set_errno(void)225 static BOOL set_errno(void)
226 {
227 	errno=b_errno_win32;
228 	return FALSE;
229 }
230 
231 // Initialize the COM infrastructure and the internal pointers.
Initialize(struct asfd * asfd,struct cntr * cntr)232 BOOL VSSClientGeneric::Initialize(struct asfd *asfd, struct cntr *cntr)
233 {
234 	if(!(p_CreateVssBackupComponents && p_VssFreeSnapshotProperties))
235 	{
236 		logw(asfd, cntr, "%s error\n", __func__);
237 		return bsystem_error();
238 	}
239 
240 	HRESULT hr;
241 	// Initialize COM.
242 	if(!m_bCoInitializeCalled)
243 	{
244 		hr=CoInitialize(NULL);
245 		if(FAILED(hr))
246 		{
247 			logw(asfd, cntr, "%s: CoInitialize returned 0x%08X\n",
248 				__func__, (unsigned int)hr);
249 			return set_errno();
250 		}
251 		m_bCoInitializeCalled=true;
252 	}
253 
254 	// Initialize COM security.
255 	if(!m_bCoInitializeSecurityCalled)
256 	{
257 		hr=CoInitializeSecurity(
258 			NULL, // Allow *all* VSS writers to communicate back!
259 			-1,   // Default COM authentication service.
260 			NULL, // Default COM authorization service.
261 			NULL, // Reserved parameter.
262 			// Strongest COM authentication level.
263 			RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
264 			// Minimal impersonation abilities.
265 			RPC_C_IMP_LEVEL_IDENTIFY,
266 			NULL, // Default COM authentication settings.
267 			EOAC_NONE, //  No special options.
268 			NULL  //  Reserved parameter.
269 			);
270 
271 		if(FAILED(hr))
272 		{
273 			logw(asfd, cntr,
274 			  "%s: CoInitializeSecurity returned 0x%08X\n",
275 			  __func__, (unsigned int)hr);
276 			return set_errno();
277 		}
278 		m_bCoInitializeSecurityCalled=true;
279 	}
280 
281 	// Release the IVssBackupComponents interface.
282 	if(m_pVssObject)
283 	{
284 		m_pVssObject->Release();
285 		m_pVssObject=NULL;
286 	}
287 
288 	// Create the internal backup components object.
289 	hr=p_CreateVssBackupComponents((IVssBackupComponents**)&m_pVssObject);
290 	if(FAILED(hr))
291 	{
292 		berrno be;
293 		berrno_init(&be);
294 		logw(asfd, cntr,
295 		  "%s: CreateVssBackupComponents returned 0x%08X. ERR=%s\n",
296 			__func__, (unsigned int)hr,
297 			berrno_bstrerror(&be, b_errno_win32));
298 		return set_errno();
299 	}
300 
301 	// 1. InitializeForBackup.
302 	hr=((IVssBackupComponents*)m_pVssObject)->InitializeForBackup();
303 	if(FAILED(hr))
304 	{
305 		logw(asfd, cntr, "%s: IVssBackupComponents->InitializeForBackup returned 0x%08X\n", __func__, (unsigned int)hr);
306 		return set_errno();
307 	}
308 
309 	// 2. SetBackupState.
310 	hr=((IVssBackupComponents*)m_pVssObject)->SetBackupState(true,
311 		true, VSS_BT_FULL, false);
312 	if(FAILED(hr))
313 	{
314 		logw(asfd, cntr, "%s: IVssBackupComponents->SetBackupState returned 0x%08X\n", __func__, (unsigned int)hr);
315 		return set_errno();
316 	}
317 
318 	CComPtr<IVssAsync> pAsync1;
319 	// 3. GatherWriterMetaData.
320 	hr=((IVssBackupComponents*)
321 		m_pVssObject)->GatherWriterMetadata(&pAsync1.p);
322 	if(FAILED(hr))
323 	{
324 		logw(asfd, cntr, "%s: IVssBackupComponents->GatherWriterMetadata returned 0x%08X\n", __func__, (unsigned int)hr);
325 		return set_errno();
326 	}
327 	WaitAndCheckForAsyncOperation(pAsync1.p);
328 
329 	return TRUE;
330 }
331 
WaitAndCheckForAsyncOperation(IVssAsync * pAsync)332 BOOL VSSClientGeneric::WaitAndCheckForAsyncOperation(IVssAsync* pAsync)
333 {
334 	// Wait until the async operation finishes
335 	// unfortunately we can't use a timeout here yet.
336 	// the interface would allow it on W2k3,
337 	// but it is not implemented yet.
338 
339 	HRESULT hr;
340 
341 	// Check the result of the asynchronous operation.
342 	HRESULT hrReturned=S_OK;
343 
344 	int timeout=600; // 10 minutes.
345 
346 	int queryErrors=0;
347 	do
348 	{
349 		if(hrReturned!=S_OK) Sleep(1000);
350 
351 		hrReturned=S_OK;
352 		hr=pAsync->QueryStatus(&hrReturned, NULL);
353 
354 		if(FAILED(hr)) queryErrors++;
355 	}
356 	while ((timeout-->0) && (hrReturned==VSS_S_ASYNC_PENDING));
357 
358 	if(hrReturned==VSS_S_ASYNC_FINISHED) return TRUE;
359 
360 #ifdef xDEBUG
361 	// Check if the async operation succeeded.
362 	if(hrReturned!=VSS_S_ASYNC_FINISHED)
363 	{
364 		wchar_t *pwszBuffer=NULL;
365 		// I don't see the usefulness of the following -- KES.
366 		FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER
367 				| FORMAT_MESSAGE_FROM_SYSTEM
368 				| FORMAT_MESSAGE_IGNORE_INSERTS,
369 				NULL, hrReturned,
370 				MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
371 				(LPWSTR)&pwszBuffer, 0, NULL);
372 
373 		LocalFree(pwszBuffer);
374 		errno=b_errno_win32;
375 	}
376 #endif
377 	return FALSE;
378 }
379 
CreateSnapshots(char * szDriveLetters)380 BOOL VSSClientGeneric::CreateSnapshots(char* szDriveLetters)
381 {
382 	// szDriveLetters contains all drive letters in uppercase.
383 	// If a drive can not being added, it's converted to lowercase in
384 	// szDriveLetters.
385 	// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vss/base/ivssbackupcomponents_startsnapshotset.asp
386 
387 	if(!m_pVssObject || m_bBackupIsInitialized) return bsystem_error();
388 
389 	m_uidCurrentSnapshotSet=GUID_NULL;
390 
391 	IVssBackupComponents *pVss=(IVssBackupComponents*)m_pVssObject;
392 
393 	pVss->StartSnapshotSet(&m_uidCurrentSnapshotSet);
394 
395 	wchar_t szDrive[3];
396 	szDrive[1]=':';
397 	szDrive[2]=0;
398 
399 	wstring volume;
400 
401 	CComPtr<IVssAsync> pAsync1;
402 	CComPtr<IVssAsync> pAsync2;
403 	VSS_ID pid;
404 
405 	for(size_t i=0; i<strlen (szDriveLetters); i++)
406 	{
407 		szDrive[0]=szDriveLetters[i];
408 		volume=GetUniqueVolumeNameForPath(szDrive);
409 		// Store uniquevolumname.
410 		if(SUCCEEDED(pVss->AddToSnapshotSet((LPWSTR)volume.c_str(),
411 		  GUID_NULL, &pid)))
412 			wcsncpy(m_wszUniqueVolumeName[szDriveLetters[i]-'A'],
413 				(LPWSTR) volume.c_str(), MAX_PATH);
414 		else
415 			szDriveLetters[i]=tolower(szDriveLetters[i]);
416 	}
417 
418 	if(FAILED(pVss->PrepareForBackup(&pAsync1.p))) return set_errno();
419 
420 	WaitAndCheckForAsyncOperation(pAsync1.p);
421 
422 	if(!CheckWriterStatus()) return set_errno();
423 
424 	if(FAILED(pVss->DoSnapshotSet(&pAsync2.p))) return set_errno();
425 
426 	WaitAndCheckForAsyncOperation(pAsync2.p);
427 
428 	QuerySnapshotSet(m_uidCurrentSnapshotSet);
429 
430 	SetVSSPathConvert(VSSPathConvert, VSSPathConvertW);
431 
432 	m_bBackupIsInitialized=true;
433 
434 	return TRUE;
435 }
436 
CloseBackup()437 BOOL VSSClientGeneric::CloseBackup()
438 {
439 	BOOL bRet=FALSE;
440 	if(!m_pVssObject) errno=ENOSYS;
441 	else
442 	{
443 		IVssBackupComponents *pVss=(IVssBackupComponents*)m_pVssObject;
444 		CComPtr<IVssAsync> pAsync;
445 
446 		SetVSSPathConvert(NULL, NULL);
447 
448 		m_bBackupIsInitialized=false;
449 
450 		if(SUCCEEDED(pVss->BackupComplete(&pAsync.p)))
451 		{
452 			WaitAndCheckForAsyncOperation(pAsync.p);
453 			bRet=TRUE;
454 		}
455 		else
456 		{
457 			errno=b_errno_win32;
458 			pVss->AbortBackup();
459 		}
460 
461 		// Get latest info about writer status.
462 		CheckWriterStatus();
463 
464 		if(m_uidCurrentSnapshotSet!=GUID_NULL)
465 		{
466 			VSS_ID idNonDeletedSnapshotID=GUID_NULL;
467 			LONG lSnapshots;
468 
469 			pVss->DeleteSnapshots(m_uidCurrentSnapshotSet,
470 					VSS_OBJECT_SNAPSHOT_SET,
471 					FALSE,
472 					&lSnapshots,
473 					&idNonDeletedSnapshotID);
474 
475 			m_uidCurrentSnapshotSet=GUID_NULL;
476 		}
477 
478 		pVss->Release();
479 		m_pVssObject=NULL;
480 	}
481 
482 	// Call CoUninitialize if the CoInitialize was performed sucesfully.
483 	if(m_bCoInitializeCalled)
484 	{
485 		CoUninitialize();
486 		m_bCoInitializeCalled=false;
487 	}
488 
489 	return bRet;
490 }
491 
492 // Query all the shadow copies in the given set.
QuerySnapshotSet(GUID snapshotSetID)493 BOOL VSSClientGeneric::QuerySnapshotSet(GUID snapshotSetID)
494 {
495 	if(!(p_CreateVssBackupComponents && p_VssFreeSnapshotProperties))
496 		return bsystem_error();
497 
498 	memset(m_szShadowCopyName,0,sizeof(m_szShadowCopyName));
499 
500 	if(snapshotSetID==GUID_NULL || m_pVssObject==NULL)
501 		return bsystem_error();
502 
503 	IVssBackupComponents *pVss=(IVssBackupComponents*)m_pVssObject;
504 
505 	// Get list all shadow copies.
506 	CComPtr<IVssEnumObject> pIEnumSnapshots;
507 	HRESULT hr=pVss->Query( GUID_NULL,
508 			VSS_OBJECT_NONE,
509 			VSS_OBJECT_SNAPSHOT,
510 			(IVssEnumObject**)(&pIEnumSnapshots));
511 
512 	// If there are no shadow copies, just return.
513 	if(FAILED(hr)) return set_errno();
514 
515 	// Enumerate all shadow copies.
516 	VSS_OBJECT_PROP Prop;
517 	VSS_SNAPSHOT_PROP& Snap=Prop.Obj.Snap;
518 
519 	while(1)
520 	{
521 		// Get the next element.
522 		ULONG ulFetched;
523 		hr=(pIEnumSnapshots.p)->Next(1, &Prop, &ulFetched);
524 
525 		// We reached the end of list.
526 		if(!ulFetched) break;
527 
528 		// Print the shadow copy (if not filtered out).
529 		if(Snap.m_SnapshotSetId == snapshotSetID)
530 		{
531 			for(int ch='A'-'A';ch<='Z'-'A';ch++)
532 			{
533 				if(!wcscmp(Snap.m_pwszOriginalVolumeName,
534 				  m_wszUniqueVolumeName[ch]))
535 				{
536 					wcsncpy(m_szShadowCopyName[ch],
537 					  Snap.m_pwszSnapshotDeviceObject,
538 					  MAX_PATH-1);
539 					break;
540 				}
541 			}
542 		}
543 		p_VssFreeSnapshotProperties(&Snap);
544 	}
545 	errno=0;
546 	return TRUE;
547 }
548 
549 // Check the status for all selected writers.
CheckWriterStatus()550 BOOL VSSClientGeneric::CheckWriterStatus()
551 {
552 	// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vss/base/ivssbackupcomponents_startsnapshotset.asp
553 	IVssBackupComponents *pVss=(IVssBackupComponents*)m_pVssObject;
554 	DestroyWriterInfo();
555 
556 	// Gather writer status to detect potential errors
557 	CComPtr<IVssAsync> pAsync;
558 
559 	HRESULT hr=pVss->GatherWriterStatus(&pAsync.p);
560 	if(FAILED(hr)) return set_errno();
561 
562 	WaitAndCheckForAsyncOperation(pAsync.p);
563 
564 	unsigned cWriters=0;
565 
566 	hr=pVss->GetWriterStatusCount(&cWriters);
567 	if(FAILED(hr)) return set_errno();
568 
569 	int nState;
570 
571 	// Enumerate each writer.
572 	for(unsigned iWriter=0; iWriter<cWriters; iWriter++)
573 	{
574 		VSS_ID idInstance=GUID_NULL;
575 		VSS_ID idWriter= GUID_NULL;
576 		VSS_WRITER_STATE eWriterStatus=VSS_WS_UNKNOWN;
577 		CComBSTR bstrWriterName;
578 		HRESULT hrWriterFailure=S_OK;
579 
580 		// Get writer status
581 		hr=pVss->GetWriterStatus(iWriter,
582 				&idInstance,
583 				&idWriter,
584 				&bstrWriterName,
585 				&eWriterStatus,
586 				&hrWriterFailure);
587 		if(FAILED(hr)) nState=0; // Unknown.
588 		else
589 		{
590 			switch(eWriterStatus)
591 			{
592 				case VSS_WS_FAILED_AT_IDENTIFY:
593 				case VSS_WS_FAILED_AT_PREPARE_BACKUP:
594 				case VSS_WS_FAILED_AT_PREPARE_SNAPSHOT:
595 				case VSS_WS_FAILED_AT_FREEZE:
596 				case VSS_WS_FAILED_AT_THAW:
597 				case VSS_WS_FAILED_AT_POST_SNAPSHOT:
598 				case VSS_WS_FAILED_AT_BACKUP_COMPLETE:
599 				case VSS_WS_FAILED_AT_PRE_RESTORE:
600 				case VSS_WS_FAILED_AT_POST_RESTORE:
601 #if defined(B_VSS_W2K3) || defined(B_VSS_VISTA)
602 				case VSS_WS_FAILED_AT_BACKUPSHUTDOWN:
603 #endif
604 					// Failed.
605 					nState=-1;
606 					break;
607 
608 				default:
609 					// OK.
610 					nState=1;
611 			}
612 		}
613 		// Store text info.
614 		char str[1000];
615 		char szBuf1[200];
616 		char szBuf2[200];
617 		char szBuf3[200];
618 		wchar_2_UTF8(szBuf1, bstrWriterName.p, sizeof(szBuf1));
619 		itoa(eWriterStatus, szBuf2, sizeof(szBuf2));
620 		wchar_2_UTF8(szBuf3, GetStringFromWriterStatus(eWriterStatus),
621 			sizeof(szBuf3));
622 		snprintf(str, sizeof(str), "\"%s\", State: 0x%s (%s)",
623 			szBuf1, szBuf2, szBuf3);
624 
625 		AppendWriterInfo(nState, (const char *)str);
626 	}
627 
628 	hr=pVss->FreeWriterStatus();
629 
630 	if(FAILED(hr)) return set_errno();
631 
632 	errno=0;
633 	return TRUE;
634 }
635 
636 #endif
637