Ticket #13 (new defect)
Fix file locking on Windows
| Reported by: | chris | Owned by: | chris |
|---|---|---|---|
| Priority: | normal | Milestone: | 0.12 |
| Component: | bbackupd | Version: | 0.10 |
| Keywords: | windows file locking locked volume shadow copy service vss | Cc: |
Description
There are two locking issues on Windows: Box cannot back up open files, and while Box is backing up a file, other applications cannot open it for writing.
Changing our access method to Volume Shadow Copy Service is probably the right fix for both of these, but it only works on XP and above, and I cannot install the SDK it on my development machine. I would also like to make it work with MinGW without requiring a restrictively-licensed SDK download from Microsoft.
Please could someone use a Windows machine (XP or above, Windows Activation compliant) to download the SDK and send it to me?
Another option (workaround) might be to use the Windows command-line tools to create a shadow copy and back that up instead of the original. However, I think that would be a kluge, difficult to install, and difficult to detect and handle errors with.
Attachments
Change History
comment:2 Changed 5 years ago by gadlen
I fetched it. It can (for now) be found here. http://lee.org/temp/Volume-Shadow-Copy-Service%20SDK-7.2-setup.exe
:-) Lee http://lee.org
Changed 5 years ago by petej
-
attachment
ShadowBox.bat
added
ShadowBox?.bat, an example of how to use Shadow Copy, vshadow.exe
comment:5 Changed 2 years ago by achim
A good example of how simple VSS-support was added to Cywgin Rsync 3.0.x (check the patch files, they seem straightforward).
I have already checked via e-mail with both Leen Besselink (the author of the page) and Elias Penttilä (the original author) and they are absolutely fine with us using their source either verbatim or as inspiration.
comment:6 Changed 2 years ago by achim
Elias Penttilä's patchset for rsync-2.6.6 http://users.tkk.fi/~epenttil/rsync-vss/
comment:7 Changed 2 years ago by achim
First ideas for VSS implementation
First off, the GPL'ed Bacula backup project introduced an abstraction layer "VSSClient" that is used to create the actual VSS depending on the underlying Windows OS, as the APIs seem slightly different for XP, 2K3 and Vista/Windows? 7. All VSS code is contained in one vss_generic.cpp which uses #ifdef to include the headers and libs for each OS, such as:
#ifdef B_VSS_XP
// #pragma message("compile VSS for Windows XP")
#define VSSClientGeneric VSSClientXP
#include "inc/WinXP/vss.h"
#include "inc/WinXP/vswriter.h"
#include "inc/WinXP/vsbackup.h"
#endif
#ifdef B_VSS_W2K3
// #pragma message("compile VSS for Windows 2003")
#define VSSClientGeneric VSSClient2003
#include "inc/Win2003/vss.h"
#include "inc/Win2003/vswriter.h"
#include "inc/Win2003/vsbackup.h"
#endif
This requires that all SDKs to be available in their respective subdirectory, i.e. the XP VSS SDK should be available in boxbackup/win32/vss/inc/WinXP/ and the one for Server 2K3 in boxbackup/win32/vss/inc/Win2003/ (see next step)
For the specific header and code files, please refer to: http://bacula.svn.sourceforge.net/viewvc/bacula/trunk/bacula/src/win32/filed/?pathrev=8346
Using Bacula's approach for VSS in Box Backup
- Install the required VSS SDKs (see "SDK Download links" below for where to get the various versions):
- Make sure that MingW can see the VSS files and folders (in this case for Server 2003):
mkdir -p /usr/local/src/boxbackup/win32/vss/ ln -s /cygdrive/c/Program\ Files/Microsoft/VSSSDK72/inc/win2003 /usr/local/src/boxbackup/win32/vss/inc/Win2003 ln -s /cygdrive/c/Program\ Files/Microsoft/VSSSDK72/lib/win2003/obj/i386 /usr/local/src/boxbackup/win32/vss/lib/Win2003 ln -s /cygdrive/c/Program\ Files/MSXML\ 4.0/inc /usr/local/src/boxbackup/win32/inc_xml
- add this to bin/bbackupd/Makefile to enable linking (in this case for Server 2003)
-lole32 -luuid win32/vss/lib/Win2003/vssapi.lib win32/vss/lib/Win2003/vss_uuid.lib
- Add VSS code to BB, based on the following functions implemented by the Bacula "API"
BOOL InitializeForBackup(); virtual BOOL CreateSnapshots(char* szDriveLetters) = 0; virtual BOOL CloseBackup() = 0; virtual const char* GetDriverName() = 0; BOOL GetShadowPath (const char* szFilePath, char* szShadowPath, int nBuflen); BOOL GetShadowPathW (const wchar_t* szFilePath, wchar_t* szShadowPath, int nBuflen); /* nBuflen in characters */ BOOL CloseBackup()
On a high level, we need to
- Create the shadow copy and receive a (global) handle to a VSSClient object
- Substitute all paths that are used for backup with the corresponding VSS path as generated by our VSSClient object
- Deinit the shadow copy once the backup is done.
Note: There is also the AlphaVSS, "a .NET class library written in C++/CLI aiming to provide a managed interface to this API. The goal is to provide an interface that is simple to use from a C# or VB.NET application, yet providing the full functionality of VSS." http://alphavss.codeplex.com/ Not sure if we can use that somehow in stock C++?
Anyway, I detected the following places in the code where Box Backup interacts with the local file system paths:
lib/common/BoxPlatform.h
// Add VSS for Win32 clients #ifdef WIN32 #ifndef USE_VSS #define USE_VSS #endif #endif
bin/bbackupd/BackupDaemon.cpp
RunSyncNow()
#ifdef USE_VSS
InitializeForBackup();
#endif
// Sync the directory
(*i)->mpDirectoryRecord->SyncDirectory(
params,
BackupProtocolClientListDirectory::RootDirectory,
(*i)->mPath, std::string("/") + (*i)->mName);
Local path is stored in (*i)->mPath
for(std::vector<Location *>::const_iterator
i(mLocations.begin());
i != mLocations.end(); ++i)
// add at the end of RunSyncNow()
#ifdef USE_VSS
CloseBackup();
#endif
Now we need to make sure that the local path used by SyncDirectory? above is actually the VSS path:
bin/bbackupd/BackupClientDirectoryRecord.cpp
SyncDirectory
Purpose: Recursively synchronise a local directory with the server.
BackupClientDirectoryRecord::SyncParams &rParams,
int64_t ContainingDirectoryID,
const std::string &rLocalPath,
const std::string &rRemotePath,
bool ThisDirHasJustBeenCreated)
Makes use of these other funcions:
UpdateItems Purpose: Update the items stored on the server. The rFiles vector will be erased after it's used to save space. Returns true if all items were updated successfully. (If not, the failures will have been logged). UploadFile Purpose: Update the items stored on the server. The rFiles vector will be erased after it's used to save space. Returns true if all items were updated successfully. (If not, the failures will have been logged).
struct dirent *en = 0; EMU_STRUCT_STAT file_st; std::string filename; while((en = ::readdir(dirHandle)) != 0)
uses MakeFullPath?
filename = MakeFullPath(rLocalPath, en->d_name);
lib/common/PathUtils.cpp
Name: PathUtils.cpp Purpose: Platform-independent path manipulation Name: MakeFullPath(const std::string& rDir, const std::string& rFile) Purpose: Combine directory and file name
is where we need to add calls to and use the result to return the correct VSS-based FullPath?:
BOOL GetShadowPath (const char* szFilePath, char* szShadowPath, int nBuflen); BOOL GetShadowPathW (const wchar_t* szFilePath, wchar_t* szShadowPath, int nBuflen); /* nBuflen in characters */
SDK Download links
For Windows XP and 2003 users, you can download Microsoft’s Volume Shadow Copy Service SDK.
Vista user’s need to download the Windows Vista SDK Update V6.0 (Microsoft Windows SDK Update for Windows Vista and .NET Framework 3.0)
http://msdn.microsoft.com/en-us/windows/bb980924.aspx
2008 Server
Windows 7 needs SDK 7.0
comment:8 Changed 2 years ago by achim
How is it done on Bacula
in win32/filed/main.cpp
/* Start up Volume Shadow Copy (only on FD) */ VSSInit();
is this really the start of the routine, or simply a helper main.cpp?
which calls win32/filed/vss.cpp
void VSSInit()
{
/* decide which vss class to initialize */
if (g_MajorVersion == 5) {
switch (g_MinorVersion) {
case 1:
g_pVSSClient = new VSSClientXP();
atexit(VSSCleanup);
return;
case 2:
g_pVSSClient = new VSSClient2003();
atexit(VSSCleanup);
return;
}
/* Vista or Longhorn or later */
// } else if (g_MajorVersion == 6 && g_MinorVersion == 0) {
} else if (g_MajorVersion >= 6) {
g_pVSSClient = new VSSClientVista();
atexit(VSSCleanup);
return;
}
}
so a global variable g_pVSSClient that contains the object for VSS control
now in filed/job.c and
#if defined(WIN32_VSS)
#include "vss.h"
static pthread_mutex_t vss_mutex = PTHREAD_MUTEX_INITIALIZER;
static int enable_vss;
#endif
#if defined(WIN32_VSS)
int vss = 0;
sscanf(dir->msg, "fileset vss=%d", &vss);
enable_vss = vss;
#endif
#if defined(WIN32_VSS)
// capture state here, if client is backed up by multiple directors
// and one enables vss and the other does not then enable_vss can change
// between here and where its evaluated after the job completes.
jcr->VSS = g_pVSSClient && enable_vss;
if (jcr->VSS) {
/* Run only one at a time */
P(vss_mutex);
}
#endif
static int backup_cmd(JCR *jcr)
#if defined(WIN32_VSS)
/* START VSS ON WIN32 */
if (jcr->VSS) {
if (g_pVSSClient->InitializeForBackup()) {
/* tell vss which drives to snapshot */
char szWinDriveLetters[27];
if (get_win32_driveletters(jcr->ff, szWinDriveLetters)) {
Jmsg(jcr, M_INFO, 0, _("Generate VSS snapshots. Driver=\"%s\", Drive(s)=\"%s\"\n"), g_pVSSClient->GetDriverName(), szWinDriveLetters);
if (!g_pVSSClient->CreateSnapshots(szWinDriveLetters)) {
Jmsg(jcr, M_WARNING, 0, _("Generate VSS snapshots failed.\n"));
jcr->JobErrors++;
} else {
/* tell user if snapshot creation of a specific drive failed */
int i;
for (i=0; i < (int)strlen(szWinDriveLetters); i++) {
if (islower(szWinDriveLetters[i])) {
Jmsg(jcr, M_WARNING, 0, _("Generate VSS snapshot of drive \"%c:\\\" failed. VSS support is disabled on this drive.\n"), szWinDriveLetters[i]);
jcr->JobErrors++;
}
}
/* inform user about writer states */
for (i=0; i < (int)g_pVSSClient->GetWriterCount(); i++)
if (g_pVSSClient->GetWriterState(i) < 1) {
Jmsg(jcr, M_WARNING, 0, _("VSS Writer (PrepareForBackup): %s\n"), g_pVSSClient->GetWriterInfo(i));
jcr->JobErrors++;
}
}
} else {
Jmsg(jcr, M_INFO, 0, _("No drive letters found for generating VSS snapshots.\n"));
}
} else {
berrno be;
Jmsg(jcr, M_WARNING, 0, _("VSS was not initialized properly. VSS support is disabled. ERR=%s\n"), be.bstrerror());
}
run_scripts(jcr, jcr->RunScripts, "ClientAfterVSS");
}
#endif
#if defined(WIN32_VSS)
/* STOP VSS ON WIN32 */
/* tell vss to close the backup session */
if (jcr->VSS) {
if (g_pVSSClient->CloseBackup()) {
/* inform user about writer states */
for (int i=0; i<(int)g_pVSSClient->GetWriterCount(); i++) {
int msg_type = M_INFO;
if (g_pVSSClient->GetWriterState(i) < 1) {
msg_type = M_WARNING;
jcr->JobErrors++;
}
Jmsg(jcr, msg_type, 0, _("VSS Writer (BackupComplete): %s\n"), g_pVSSClient->GetWriterInfo(i));
}
}
V(vss_mutex);
}
#endif
and in filed/status.c
#ifdef WIN32_VSS
#include "vss.h"
#define VSS " VSS"
extern VSSClient *g_pVSSClient;
#else
#define VSS ""
#endif
len = Mmsg(msg, "VSS %s, Priv 0x%x\n", g_pVSSClient?"enabled":"disabled", privs);
#ifdef WIN32_VSS
if (g_pVSSClient && g_pVSSClient->IsInitialized()) {
vss = "VSS ";
}
#endif

The SDK can be found here.