前面一篇文章简单阐述了备份模块的基本原理,本文要分析代码了,其实这个代码我感觉写的很烂,一来是因为时间仓促,二来是因为我根本不想写的太好,因为我们的主模块写的就不好,如果一个边角料写的太好就有点喧宾夺主的味道,公司有些人得罪不起,因此只好在vc++的.cpp文件中嵌入所谓带着.cpp后缀名的标准c程序。以下是完全备份的代码逻辑:
int FullBakup()
{
int ret = BAKUP_RESULT_OK;
RESULT result_full;
memset( &result_full,0,sizeof(RESULT) );
QueryByType( 3, &result_full, 1 ); //查询数据库,得到备份指导信息
...//判断从数据库中得到的需求的合法性
EnterCriticalSection(&g_criticalSection);
ret = FullBak( &result_full ); //进入完全备份
LeaveCriticalSection(&g_criticalSection);
return ret;
}
int FullBak(PRESULT result) //完全备份
{
BAK_FILE_INFO *g_pBakInfoHash = NULL; //内存中的备份信息,是一个数组
g_pBakInfoHash = (BAK_FILE_INFO*)malloc( 128*4096*sizeof(BAK_FILE_INFO) );
char path[MAX_PATH];char * root = "//";
strcpy_s(path,root);
clearit(g_pBakInfoHash);
strcpy(OriDir,result->strStationDir);
BakDirFiles( path, result->strStationDir,g_pBakInfoHash ); //见下面
char strCurrTime[13]; //获取当前时间戳
memset( strCurrTime, 0, 13 );
CTime tm=CTime::GetCurrentTime();
CString str;
...//将str格式化为YYYYMMDDHHMM的时间格式
strcpy_s(strCurrTime,str.GetBuffer());
int ret = ValidDir( result->strStationDir, result->strFullBakupDir );
if( ret != BAKUP_RESULT_OK)
{
...//出错处理并返回
}
ret = DisposeRar( 1, strCurrTime, result->strStationDir, result->strFullBakupDir );//产生压缩包
if( ret != BAKUP_RESULT_OK)
{
...//出错处理并返回
}
ret = WriteBakInfoToFile( strCurrTime, result->strFullBakupDir,result->strStationDir, result->strLocalIP,g_pBakInfoHash ); //保存完全备份的信息,以备差异备份或者恢复时使用,格式为首先写入当前的时间YYYYMMDDHHMM,然后依次写入BAK_FILE_INFO数组的每一条有效记录
if( ret != BAKUP_RESULT_OK )
{
...//出错处理并返回
}
return BAKUP_RESULT_OK;
}
以下函数获取备份目录的文件信息,并且导入一个BAK_FILE_INFO结构体数组中,该数组作为以下函数的输出参数:
DWORD BakDirFiles( char * dir,char *strOriDirIt,BAK_FILE_INFO *g_pBakInfoHash )
{
_WIN32_FIND_DATAA FindFileData;
HANDLE hFind = INVALID_HANDLE_VALUE;
DWORD dwError;
char curr[MAX_PATH];char currs[MAX_PATH];
memset(curr,0,MAX_PATH);memset(currs,0,MAX_PATH);
strcpy_s(curr,dir);strcpy_s(currs,strOriDirIt);
strcat_s(currs,curr);strncat_s(currs, "*", 3);
hFind = FindFirstFileA(currs, &FindFileData);
...//出错处理
else
{
while (FindNextFileA(hFind, &FindFileData) != 0)
{
if (!strcmp(FindFileData.cFileName,"..")||!strcmp(FindFileData.cFileName,"."))
{//跳过.和..
continue;
}
if ((FindFileData.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY) == false)
{
char temp[MAX_PATH];
memset(temp,0,MAX_PATH);
strcat_s(temp,strOriDirIt);strcat_s(temp,curr);strcat_s(temp,FindFileData.cFileName);
UINT uFileNo = GetFncode(temp); //由文件全路径得到文件编码
int i =0,iFound = 0;
unsigned int v = uFileNo%128;//计算该文件在BAK_FILE_INFO二维数组中的行位置
for( i = 0; i < 4096; i++ ) //列位置通过遍历寻找一个空闲的位置
{
PBAK_FILE_INFO bfi = g_pBakInfoHash+128*v+i;
if( bfi->used && bfi->strFileName && !strcmp(bfi->strFileName, temp) )
{//如果该文件已经存在于这个二维数组,则更新
bfi->flag = 0;
bfi->used = 1;
bfi->iLastWriteHigh = FindFileData.ftLastWriteTime.dwHighDateTime;
bfi->iLastWriteLow = FindFileData.ftLastWriteTime.dwLowDateTime;
break;
}
else if( bfi->used == 0 )
{//如果不存在,则插入新纪录
g_count++;
memset(bfi,0,sizeof(BAK_FILE_INFO));
bfi->used = 1;
bfi->flag = 0;
strcpy_s(bfi->strFileName,temp);
...//将文件访问时间和文件编码等信息存入bfi
break;
}
}
}
else if (FindFileData.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY)
{
char temp[MAX_PATH];
memset(temp,0,MAX_PATH);
strcat_s(temp,curr);strcat_s(temp,FindFileData.cFileName);strcat(temp,"//");
BakDirFiles(temp,strOriDirIt, g_pBakInfoHash); //递归文件树
}
}
FindClose(hFind);
if (GetLastError() != ERROR_NO_MORE_FILES)
{
return BAKUP_RESULT_ERR;
}
}
return BAKUP_RESULT_OK;
}
以下是差分备份的代码逻辑:
int SubBak(PRESULT result) //差分备份
{
int ret = BAKUP_RESULT_OK;
...//以前如果没有完全备份,那么不能做差分备份,只能做完完全备份之后返回
BAK_FILE_INFO *g_pBakInfoHash = NULL;//[128][4096] = {0};
g_pBakInfoHash = (BAK_FILE_INFO*)malloc( 128*4096*sizeof(BAK_FILE_INFO) );
//建立差分备份的临时目录
strcpy( SubDir, "c://tempbak" );
CreateDirectoryA(SubDir,NULL);
char strCurrTime[13];//得到当前时间戳
memset( strCurrTime, 0, 13 );
CTime tm=CTime::GetCurrentTime();
CString str;
...//str格式化,和完全备份相同
strcpy_s(strCurrTime,str.GetBuffer());
char strLastFullTime[13];//得到最近一次的全备份时间戳
memset( strLastFullTime, 0, 13 );
ret = ReadBakInfoFromFile( strLastFullTime, result->strStationDir, result->strLocalIP,g_pBakInfoHash );
...//出错处理
char path[MAX_PATH];
char * root = "//";
strcpy_s(path,root);
int i = 0;
strcpy( OriDir, result->strStationDir );
ListFilesAndCompare(path,i,result->strStationDir,g_pBakInfoHash);//其实就是BakDirFiles的变形,为了形象,特意用一个不同的函数取代之,见下面解析
//压缩差分备份的临时目录并且存放
SearchForDeleted(g_pBakInfoHash); //找到在上次备份以来被删除的文件
ret = ValidDir( result->strStationDir,result->strSubBakupDir );
if( ret != BAKUP_RESULT_OK )
{
...//删除临时目录,处理错误,释放资源并返回
}
ret = DisposeRar( 1, strCurrTime, SubDir, result->strSubBakupDir );
if( ret != BAKUP_RESULT_OK)
{
...//删除临时目录,处理错误,释放资源并返回
}
//将信息存入数据库的bakup_info表
ret = WriteInfoToDatabase( strLastFullTime, result->strFullBakupDir,strCurrTime, result->strSubBakupDir, result->strStationDir,result->strLocalIP,g_pBakInfoHash );
if( ret != BAKUP_RESULT_OK )
{
...//处理错误,释放资源并返回
}
RemoveSubDir( SubDir ); //删除差分备份的临时目录
...//处理错误,释放资源并返回
}
DWORD ListFilesAndCompare( char * dir, int & iDelete, char * strOriDirIt,BAK_FILE_INFO *g_pBakInfoHash )
{
int iNewDir = 0;
...//和BakDirFiles相同,直到下面的判断,以下省略代码前面的缩进,实际上应该在一个缩进很长的for循环里面
if ((FindFileData.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY) == false)
{
char temp[MAX_PATH];
memset(temp,0,MAX_PATH);
strcat_s(temp,strOriDirIt);strcat_s(temp,curr);strcat_s(temp,FindFileData.cFileName);
UINT uFileNo = GetFncode(temp);
int i =0,iFound = 0;
unsigned int v = uFileNo%128;
for( i = 0; i < 4096; i++ )
{
BAK_FILE_INFO bfi = *(g_pBakInfoHash+128*v+i);
if( !bfi.flag && !strcmp(bfi.strFileName, temp) && bfi.iFileNo == uFileNo &&
bfi.iLastWriteHigh == FindFileData.ftLastWriteTime.dwHighDateTime &&
bfi.iLastWriteLow == FindFileData.ftLastWriteTime.dwLowDateTime )
{ //找到完全一样的文件,那么就不需要备份了,将iFound设置为1
((PBAK_FILE_INFO)(g_pBakInfoHash+128*v+i))->flag = 1;
iFound = 1;
break; //已经找到就不需要继续遍历同一行的相同文件编码的文件了
}
else if(!bfi.flag && !strcmp(bfi.strFileName, temp) && bfi.iFileNo == uFileNo)
{ //文件名称一样,并且flag为0,说明没有被访问过,现在找到了,将flag设置为1,说明已经访问过了,但是最后访问时间变了,因此不将iFound设置为1,这就是说要备份该文件
((PBAK_FILE_INFO)(g_pBakInfoHash+128*v+i))->flag = 1;
}
}
if( iFound )
continue;
else //备份的过程
{
iDelete = 1; //将iDelete设置为1,说明该目录有文件被备份了,在上层目录不要删除了它,否则就需要删除这个目录,因为该备份程序不备份空目录,注意这个iDelete是递归设置的,只要有一个子目录中有文件被备份就不删除它。
char tempsubpath[MAX_PATH];
memset(tempsubpath,0,MAX_PATH);
strcpy_s(tempsubpath,SubDir);strcat_s(tempsubpath,curr);
strcat_s(tempsubpath,FindFileData.cFileName);
CopyFileA(temp,tempsubpath,false);
}
}
else if (FindFileData.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY)
{
char tempsubpath[MAX_PATH];
memset(tempsubpath,0,MAX_PATH);
strcpy_s(tempsubpath,SubDir);strcat_s(tempsubpath,curr);
strcat_s(tempsubpath,FindFileData.cFileName);
//在差分备份的临时目录中实时建立新目录
if(CreateDirectoryA(tempsubpath,NULL))
{
iNewDir = 1;
}
char temp[MAX_PATH];
memset(temp,0,MAX_PATH);
strcat_s(temp,curr);strcat_s(temp,FindFileData.cFileName);
iDelete = 0;
strcat(temp,"//");
ListFilesAndCompare(temp,iDelete,strOriDirIt,g_pBakInfoHash);
if( !iDelete && iNewDir ) //如果子目录是空的,说明子目录没有文件需要备份,删除新建目录
RemoveDirectoryA( tempsubpath );
}
...//返回操作和BakDirFiles相同
}
以下的函数就是搜集被删除文件的:
void SearchForDeleted(BAK_FILE_INFO* g_pBakInfoHash)
{
char temp[MAX_PATH];
memset(temp,0,MAX_PATH);
strcat_s(temp,SubDir);strcat_s(temp,"//");strcat_s(temp,"1qaz2wsx0okm");
FILE * fp ;
errno_t errn = fopen_s(&fp,temp,"wc");
...//出错处理
int i,j;
for( i = 0; i<128; i++ )
for( j = 0; j<4096; j++ )
{
PBAK_FILE_INFO bfi = (g_pBakInfoHash+128*i+j);
if( bfi->flag )
continue;
else if(bfi->used) //寻找所有有效bfi中的flag为0的bfi,将之写入文件。
{
fwrite( bfi->strFileName, bfi->iFileNameLen, 1, fp );
fwrite( "/r/n", 2, 1, fp );
bfi->used = 0;
}
}
fclose(fp);
}
以下是备份恢复的逻辑,注意,备份恢复是按照YYYYMMDDHHMM时间来恢复的,并不区分是完全备份还是差分备份:
int Recover() //恢复
{
int ret = BAKUP_RESULT_OK;
char strResult[13];
RESULT result;
memset( strResult, 0, 13 );memset( &result, 0, sizeof(RESULT) );
QueryByType( 5, &result, 1 );
...//验证指导信息的合法性
strcpy( result.strRecoverSub, result.strTimeTo );
...
if(... && strcmp(result.strRecoverFull,"") && strcmp( result.strRecoverFull,result.strTimeTo ) )
{//如果完全备份的时间和差分备份的时间不同,则说明需要首先恢复完全备份
if(!DisposeRar( 0, result.strRecoverFull,result.strRecoverDir,result.strFullBakupDir ))
...//出错以及善后处理
}
...//以下无论如何都要恢复差分备份
ret = DisposeRar( 0, result.strRecoverSub,result.strRecoverDir,result.strSubBakupDir );
...//出错以及善后处理
DeleteLS( result.strRecoverDir, result.strStationDir ); //删除需要删除的文件,依据就是差分备份时搜集到的需要删除的文件,可以随意存放,我是将其存放在差分备份的压缩包里面了
return BAKUP_RESULT_OK;
}
完毕,代码逻辑介绍到此结束,原本这个模块中还有自动备份设置,这里就不多说了,记得我曾经写过一篇关于应用程序timer的文章,就是这个备份模块中自动备份相关的,这里就不说了,无非就是单独开一个线程,然后设置几个timer即可。