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