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