Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

780 lines
17 KiB

  1. #define DEFINED_UNICODE
  2. #include "pch.h"
  3. #ifdef CSC_ON_NT
  4. #pragma hdrstop
  5. #include <winioctl.h>
  6. #endif //CSC_ON_NT
  7. #include "shdcom.h"
  8. #include "shdsys.h"
  9. #include "reint.h"
  10. #include "lib3.h"
  11. #include "strings.h"
  12. #include "oslayeru.h"
  13. #include "record.h"
  14. #include "utils.h"
  15. #define MAX_SHADOW_DIR_NAME 16
  16. typedef unsigned long ulong;
  17. typedef unsigned short ushort;
  18. typedef VOID (*PRINTPROC)(LPSTR);
  19. typedef struct tagRBA
  20. {
  21. unsigned ulidShadow; // Inode which is represented by this structure
  22. GENERICHEADER sGH; // it's header
  23. CSCHFILE hf; // open handle to the file
  24. DWORD cntRBE; // count of buffer entries in the array
  25. DWORD cntRecsPerRBE; // #of records per buffer entry
  26. DWORD cbRBE; // size in bytes of each buffer entry
  27. LPBYTE rgRBE[]; // buffer entry array
  28. }
  29. RBA, *LPRBA; // stands for RecordBuffArray
  30. #define MAX_RECBUFF_ENTRY_SIZE (0x10000-0x100)
  31. #define MAX_RBES_EXPECTED 1024
  32. #define INODE_NULL 0L
  33. #define UPDATE_REC 1
  34. #define FIND_REC 2
  35. #define DELETE_REC 3
  36. #define ALLOC_REC 4
  37. #define SHADOW_FILE_NAME_SIZE 8
  38. extern BOOL vfCSCEnabled;
  39. extern DWORD vdwAgentThreadId;
  40. extern char vszDBDir[MAX_PATH];
  41. static char szBackslash[] = "\\";
  42. static const char vszWorstCaseDefDir[] ="c:\\csc\\";
  43. // directory name where the CSC database lives
  44. // subdirectories under CSC directory
  45. char szStarDotStar[]="*.*";
  46. char vchBuff[256], vchPrintBuff[1024];
  47. extern DWORD vdwDBCapacity, vdwClusterSize;
  48. #define RecFromInode(hShadow) ((hShadow & 0x7fffffff) - (ULID_FIRST_USER_DIR-1))
  49. AssertData;
  50. AssertError;
  51. int DoDBMaintenance(VOID);
  52. int CALLBACK CheckDBProc(
  53. int cntDrives,
  54. DWORD dwCookie
  55. );
  56. int PUBLIC CheckPQ(VOID);
  57. int CountProc(HANDLE hf, LPQHEADER lpQH, LPQREC lpQR, unsigned *lpcnt);
  58. BOOL
  59. FindCreateDBDir(
  60. BOOL *lpfCreated,
  61. BOOL fCleanup // empty the directory if found
  62. );
  63. BOOL
  64. DeleteDirectoryFiles(
  65. LPCSTR lpszDir
  66. );
  67. int ClearShadowCache();
  68. int CALLBACK ReinitShadowDBProc(
  69. int cntDrives,
  70. DWORD dwCookie
  71. );
  72. BOOL IsValidName(LPSTR lpName);
  73. int SetDefaultSpace(LPSTR lpShadowDir);
  74. VOID
  75. PrintShareHeader(
  76. LPSHAREHEADER lpSH,
  77. PRINTPROC lpfnPrintProc
  78. );
  79. VOID
  80. PrintPQHeader(
  81. LPQHEADER lpQH,
  82. PRINTPROC lpfnPrintProc
  83. );
  84. VOID
  85. PrintFileHeader(
  86. LPFILEHEADER lpFH,
  87. unsigned ulSpaces,
  88. PRINTPROC lpfnPrintProc
  89. );
  90. VOID PrintShareRec(
  91. unsigned ulRec,
  92. LPSHAREREC lpSR,
  93. PRINTPROC lpfnPrintProc
  94. );
  95. VOID PrintFilerec(
  96. unsigned ulRec,
  97. LPFILERECEXT lpFR,
  98. unsigned ulSpaces,
  99. PRINTPROC lpfnPrintProc
  100. );
  101. VOID
  102. PrintPQrec(
  103. unsigned ulRec,
  104. LPQREC lpQrec,
  105. PRINTPROC lpfnPrintProc
  106. );
  107. int
  108. PrintSpaces(
  109. LPSTR lpBuff,
  110. unsigned ulSpaces
  111. );
  112. #ifdef CSC_ON_NT
  113. BOOL
  114. PUBLIC
  115. CheckCSCShare(
  116. USHORT *lptzShare,
  117. LPCSCPROC lpfnMergeProgress,
  118. DWORD dwContext
  119. );
  120. #else
  121. BOOL
  122. PUBLIC
  123. CheckCSCShare(
  124. LPSTR lptzShare,
  125. LPCSCPROC lpfnMergeProgress,
  126. DWORD dwContext
  127. );
  128. #endif
  129. #ifndef CSC_ON_NT
  130. BOOL
  131. CheckCSCDatabaseVersion(
  132. BOOL *lpfWasDirty
  133. )
  134. {
  135. char *lpszName = NULL;
  136. SHAREHEADER sSH;
  137. PRIQHEADER sPQ;
  138. CSCHFILE hfShare = 0, hfPQ=0;
  139. BOOL fOK = FALSE;
  140. DWORD dwErrorShare=NO_ERROR, dwErrorPQ=NO_ERROR;
  141. // OutputDebugStringA("Checking version...\r\n");
  142. lpszName = FormNameString(vszDBDir, ULID_SHARE);
  143. if (!lpszName)
  144. {
  145. return FALSE;
  146. }
  147. if(!(hfShare = OpenFileLocal(lpszName)))
  148. {
  149. dwErrorShare = GetLastError();
  150. }
  151. FreeNameString(lpszName);
  152. lpszName = FormNameString(vszDBDir, ULID_PQ);
  153. if (!lpszName)
  154. {
  155. goto bailout;
  156. }
  157. if(!(hfPQ = OpenFileLocal(lpszName)))
  158. {
  159. dwErrorPQ = GetLastError();
  160. }
  161. FreeNameString(lpszName);
  162. lpszName = NULL;
  163. if ((dwErrorShare == NO_ERROR)&&(dwErrorPQ==NO_ERROR))
  164. {
  165. if(ReadFileLocal(hfShare, 0, &sSH, sizeof(SHAREHEADER))!=sizeof(SHAREHEADER))
  166. {
  167. //error message
  168. goto bailout;
  169. }
  170. if (sSH.ulVersion != CSC_DATABASE_VERSION)
  171. {
  172. goto bailout;
  173. }
  174. if (lpfWasDirty)
  175. {
  176. *lpfWasDirty = ((sSH.uFlags & FLAG_SHAREHEADER_DATABASE_OPEN) != 0);
  177. }
  178. // reset the database open flag
  179. sSH.uFlags &= ~FLAG_SHAREHEADER_DATABASE_OPEN;
  180. // don't worry about any errors here
  181. WriteFileLocal(hfShare, 0, &sSH, sizeof(SHAREHEADER));
  182. if(ReadFileLocal(hfPQ, 0, &sPQ, sizeof(PRIQHEADER))!=sizeof(PRIQHEADER))
  183. {
  184. //error message
  185. goto bailout;
  186. }
  187. if (sPQ.ulVersion != CSC_DATABASE_VERSION)
  188. {
  189. goto bailout;
  190. }
  191. fOK = TRUE;
  192. }
  193. else
  194. {
  195. if (((dwErrorShare == ERROR_FILE_NOT_FOUND)&&(dwErrorPQ==ERROR_FILE_NOT_FOUND))||
  196. ((dwErrorShare == ERROR_PATH_NOT_FOUND)&&(dwErrorPQ==ERROR_PATH_NOT_FOUND)))
  197. {
  198. fOK = TRUE;
  199. }
  200. }
  201. bailout:
  202. if (lpszName)
  203. {
  204. FreeNameString(lpszName);
  205. }
  206. if (hfShare)
  207. {
  208. CloseFileLocal(hfShare);
  209. }
  210. if (hfPQ)
  211. {
  212. CloseFileLocal(hfPQ);
  213. }
  214. return (fOK);
  215. }
  216. BOOL
  217. UpgradeCSCDatabase(
  218. LPSTR lpszDir
  219. )
  220. {
  221. BOOL fCreated;
  222. return (FindCreateDBDir(&fCreated, TRUE)); // cleanup dirs if exist
  223. }
  224. BOOL
  225. FindCreateDBDirEx(
  226. BOOL *lpfCreated,
  227. BOOL *lpfIncorrectSubdirs,
  228. BOOL fCleanup // empty the directory if found
  229. )
  230. /*++
  231. Routine Description:
  232. Parameters:
  233. Return Value:
  234. Notes:
  235. --*/
  236. {
  237. DWORD dwAttr;
  238. BOOL fRet = FALSE;
  239. char buff[MAX_PATH+1];
  240. int i;
  241. UINT lenDir;
  242. *lpfIncorrectSubdirs = *lpfCreated = FALSE;
  243. Assert(vszDBDir[0]);
  244. DEBUG_PRINT(("InbCreateDir: looking for %s \r\n", vszDBDir));
  245. if ((dwAttr = GetFileAttributesA(vszDBDir)) == 0xffffffff)
  246. {
  247. DEBUG_PRINT(("InbCreateDir: didnt' find the CSC directory trying to create one \r\n"));
  248. if(CreateDirectoryA(vszDBDir, NULL))
  249. {
  250. *lpfCreated = TRUE;
  251. }
  252. else
  253. {
  254. goto bailout;
  255. }
  256. }
  257. else
  258. {
  259. if (dwAttr & FILE_ATTRIBUTE_DIRECTORY)
  260. {
  261. if (fCleanup && !DeleteDirectoryFiles(vszDBDir))
  262. {
  263. goto bailout;
  264. }
  265. }
  266. else
  267. {
  268. goto bailout;
  269. }
  270. }
  271. strcpy(buff, vszDBDir);
  272. lenDir = strlen(buff);
  273. buff[lenDir++] = '\\';
  274. buff[lenDir++] = CSCDbSubdirFirstChar();
  275. buff[lenDir++] = '1';
  276. buff[lenDir] = 0;
  277. for (i=0; i<CSCDB_SUBDIR_COUNT; ++i)
  278. {
  279. if ((dwAttr = GetFileAttributesA(buff)) == 0xffffffff)
  280. {
  281. *lpfIncorrectSubdirs = TRUE;
  282. DEBUG_PRINT(("InbCreateDir: didnt' find the CSC directory trying to create one \r\n"));
  283. if(!CreateDirectoryA(buff, NULL))
  284. {
  285. goto bailout;
  286. }
  287. }
  288. else
  289. {
  290. if (!(dwAttr & FILE_ATTRIBUTE_DIRECTORY))
  291. {
  292. goto bailout;
  293. }
  294. if (fCleanup && !DeleteDirectoryFiles(buff))
  295. {
  296. goto bailout;
  297. }
  298. }
  299. buff[lenDir-1]++;
  300. }
  301. fRet = TRUE;
  302. bailout:
  303. return (fRet);
  304. }
  305. BOOL
  306. FindCreateDBDir(
  307. BOOL *lpfCreated,
  308. BOOL fCleanup // empty the directory if found
  309. )
  310. /*++
  311. Routine Description:
  312. Parameters:
  313. Return Value:
  314. Notes:
  315. --*/
  316. {
  317. BOOL fIncorrectSubdirs = FALSE, fRet;
  318. if (fRet = FindCreateDBDirEx(lpfCreated, &fIncorrectSubdirs, fCleanup))
  319. {
  320. // if the root directory wasn't created and there in correct subdirs
  321. // then we need to recreate the database.
  322. if (!*lpfCreated && fIncorrectSubdirs)
  323. {
  324. fRet = FindCreateDBDirEx(lpfCreated, &fIncorrectSubdirs, TRUE);
  325. }
  326. }
  327. return fRet;
  328. }
  329. BOOL
  330. DeleteDirectoryFiles(
  331. LPCSTR lpszDir
  332. )
  333. {
  334. WIN32_FIND_DATAA sFind32;
  335. char buff[MAX_PATH+32];
  336. HANDLE hFind;
  337. int lenDir;
  338. BOOL fOK = TRUE;
  339. lstrcpyA(buff, lpszDir);
  340. lenDir = lstrlenA(buff);
  341. if (!lenDir)
  342. {
  343. return (FALSE);
  344. }
  345. if (buff[lenDir-1] != '\\')
  346. {
  347. buff[lenDir++] ='\\';
  348. buff[lenDir]=0;
  349. }
  350. lstrcatA(buff, szStarDotStar);
  351. if ((hFind = FindFirstFileA(buff, &sFind32))!=INVALID_HANDLE_VALUE)
  352. {
  353. do
  354. {
  355. buff[lenDir] = 0;
  356. if (!(sFind32.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
  357. && IsValidName(sFind32.cFileName))
  358. {
  359. lstrcatA(buff, sFind32.cFileName);
  360. fOK = DeleteFileA(buff);
  361. if (!fOK)
  362. {
  363. break;
  364. }
  365. }
  366. }
  367. while(FindNextFileA(hFind, &sFind32));
  368. FindClose(hFind);
  369. }
  370. return (fOK);
  371. }
  372. BOOL IsValidName(LPSTR lpName)
  373. {
  374. int len = strlen(lpName), ch, i=0;
  375. if (len != INODE_STRING_LENGTH)
  376. {
  377. return FALSE;
  378. }
  379. while (len--)
  380. {
  381. ++i;
  382. ch = *lpName++;
  383. if (!(((ch>='0') && (ch <='9'))||
  384. ((ch>='A') && (ch <='F'))||
  385. ((ch>='a') && (ch <='f'))))
  386. {
  387. return FALSE;
  388. }
  389. }
  390. if (i != INODE_STRING_LENGTH)
  391. {
  392. return FALSE;
  393. }
  394. return (TRUE);
  395. }
  396. #endif
  397. BOOL WINAPI CheckCSCEx(
  398. LPSTR lpszShare,
  399. LPCSCPROC lpfnProgress,
  400. DWORD dwContext,
  401. DWORD dwType
  402. )
  403. {
  404. #ifdef DEBUG
  405. #ifdef CSC_ON_NT
  406. USHORT uBuff[MAX_PATH];
  407. memset(uBuff, 0, sizeof(uBuff));
  408. if (MultiByteToWideChar(CP_ACP, 0, lpszShare, strlen(lpszShare), uBuff, sizeof(uBuff)))
  409. {
  410. return(CheckCSCShare(uBuff, lpfnProgress, dwContext));
  411. }
  412. #else
  413. return(CheckCSCShare(lpszShare, lpfnProgress, dwContext));
  414. #endif
  415. #else
  416. return FALSE;
  417. #endif
  418. return FALSE;
  419. }
  420. BOOL
  421. WINAPI
  422. CheckCSC(
  423. LPSTR lpszDBDir,
  424. BOOL fFix
  425. )
  426. {
  427. LPVOID lpdbID = NULL;
  428. BOOL fRet = FALSE;
  429. char szDBDir[MAX_PATH+1];
  430. DWORD dwDBCapacity;
  431. DWORD dwClusterSize;
  432. ULONG ulDatabaseStatus;
  433. // if we are the agent and CSC is enabled then bailout;
  434. if (vdwAgentThreadId)
  435. {
  436. if (vfCSCEnabled)
  437. {
  438. DEBUG_PRINT(("CheckCSC: CSC is enabled, cannot do database check\r\n"));
  439. return FALSE;
  440. }
  441. }
  442. else
  443. {
  444. unsigned ulSwitch = SHADOW_SWITCH_SHADOWING;
  445. if(ShadowSwitches(INVALID_HANDLE_VALUE, &ulSwitch, SHADOW_SWITCH_GET_STATE))
  446. {
  447. if((ulSwitch & SHADOW_SWITCH_SHADOWING)!=0)
  448. {
  449. DEBUG_PRINT(("CheckCSC: CSC is enabled, cannot do database check\r\n"));
  450. return FALSE;
  451. }
  452. }
  453. }
  454. if (InitValues(szDBDir, sizeof(szDBDir), &dwDBCapacity, &dwClusterSize))
  455. {
  456. if (!(lpdbID = OpenRecDB((lpszDBDir)?lpszDBDir:szDBDir, "Test", 0, dwDBCapacity, dwClusterSize, FALSE, NULL, &ulDatabaseStatus)))
  457. {
  458. DEBUG_PRINT(("CheckCSC: failed to open record database at %s\r\n", (lpszDBDir)?lpszDBDir:szDBDir));
  459. goto bailout;
  460. }
  461. fRet = TraverseHierarchy(lpdbID, fFix);
  462. }
  463. bailout:
  464. if (lpdbID)
  465. {
  466. CloseRecDB(lpdbID);
  467. }
  468. return (fRet);
  469. }
  470. VOID
  471. PrintShareHeader(
  472. LPSHAREHEADER lpSH,
  473. PRINTPROC lpfnPrintProc
  474. )
  475. {
  476. int iRet=0;
  477. if (lpfnPrintProc)
  478. {
  479. iRet += wsprintfA(vchPrintBuff,"****ShareHeader****\r\n" );
  480. iRet+=wsprintfA(vchPrintBuff+iRet,"Header: Flags=%xh Version=%lxh Records=%ld Size=%d \r\n",
  481. lpSH->uchFlags, lpSH->ulVersion, lpSH->ulRecords, lpSH->uRecSize);
  482. iRet+=wsprintfA(vchPrintBuff+iRet,"Store: Max=%ld Current=%ld \r\n", lpSH->sMax.ulSize, lpSH->sCur.ulSize);
  483. iRet+=wsprintfA(vchPrintBuff+iRet,"\r\n");
  484. (lpfnPrintProc)(vchPrintBuff);
  485. }
  486. }
  487. VOID
  488. PrintPQHeader(
  489. LPQHEADER lpQH,
  490. PRINTPROC lpfnPrintProc
  491. )
  492. {
  493. int iRet=0;
  494. if (lpfnPrintProc)
  495. {
  496. iRet += wsprintfA(vchPrintBuff+iRet,"****PQHeader****\r\n" );
  497. iRet += wsprintfA(vchPrintBuff+iRet,"Flags=%xh Version=%lxh Records=%ld Size=%d head=%ld tail=%ld\r\n",
  498. lpQH->uchFlags, lpQH->ulVersion, lpQH->ulRecords, lpQH->uRecSize, lpQH->ulrecHead, lpQH->ulrecTail);
  499. iRet += wsprintfA(vchPrintBuff+iRet,"\r\n");
  500. (lpfnPrintProc)(vchPrintBuff);
  501. }
  502. }
  503. VOID
  504. PrintFileHeader(
  505. LPFILEHEADER lpFH,
  506. unsigned ulSpaces,
  507. PRINTPROC lpfnPrintProc
  508. )
  509. {
  510. int iRet=0;
  511. if (lpfnPrintProc)
  512. {
  513. iRet += PrintSpaces(vchPrintBuff+iRet, ulSpaces);
  514. iRet += wsprintfA(vchPrintBuff+iRet,"****FileHeader****\r\n" );
  515. iRet += PrintSpaces(vchPrintBuff+iRet, ulSpaces);
  516. iRet += wsprintfA(vchPrintBuff+iRet,"Flags=%xh Version=%lxh Records=%ld Size=%d\r\n",
  517. lpFH->uchFlags, lpFH->ulVersion, lpFH->ulRecords, lpFH->uRecSize);
  518. iRet += PrintSpaces(vchPrintBuff+iRet, ulSpaces);
  519. iRet += wsprintfA(vchPrintBuff+iRet,"bytes=%ld entries=%d Share=%xh Dir=%xh\r\n",
  520. lpFH->ulsizeShadow, lpFH->ucShadows, lpFH->ulidShare, lpFH->ulidDir);
  521. iRet += wsprintfA(vchPrintBuff+iRet,"\r\n");
  522. (lpfnPrintProc)(vchPrintBuff);
  523. }
  524. }
  525. VOID
  526. PrintPQrec(
  527. unsigned ulRec,
  528. LPQREC lpQrec,
  529. PRINTPROC lpfnPrintProc
  530. )
  531. {
  532. int iRet=0;
  533. if (lpfnPrintProc)
  534. {
  535. iRet += wsprintfA(vchPrintBuff+iRet,"rec=%xh: Srvr=%xh dir=%xh shd=%xh prev=%xh next=%xh Stts=%xh, RfPr=%d PnCnt=%x PnFlgs=%xh DrEntr=%d\r\n"
  536. ,ulRec
  537. , lpQrec->ulidShare
  538. , lpQrec->ulidDir
  539. , lpQrec->ulidShadow
  540. , lpQrec->ulrecPrev
  541. , lpQrec->ulrecNext
  542. , (unsigned long)(lpQrec->usStatus)
  543. , (unsigned long)(lpQrec->uchRefPri)
  544. , (unsigned long)(lpQrec->uchHintPri)
  545. , (unsigned long)(lpQrec->uchHintFlags)
  546. , lpQrec->ulrecDirEntry
  547. );
  548. (lpfnPrintProc)(vchPrintBuff);
  549. }
  550. }
  551. VOID PrintShareRec(
  552. unsigned ulRec,
  553. LPSHAREREC lpSR,
  554. PRINTPROC lpfnPrintProc
  555. )
  556. {
  557. int iRet=0;
  558. if (lpfnPrintProc)
  559. {
  560. iRet += wsprintfA(vchPrintBuff+iRet,"Type=%c Flags=%xh hShare=%lxh Root=%0lxh status=%xh Share=%s \r\n"
  561. , lpSR->uchType, (unsigned)lpSR->uchFlags, ulRec, lpSR->ulidShadow
  562. , lpSR->uStatus, lpSR->rgPath);
  563. iRet += wsprintfA(vchPrintBuff+iRet,"Hint: HintFlags=%xh\r\n",
  564. (unsigned)(lpSR->uchHintFlags));
  565. iRet += wsprintfA(vchPrintBuff+iRet, "\r\n");
  566. (lpfnPrintProc)(vchPrintBuff+iRet);
  567. }
  568. }
  569. VOID PrintFilerec(
  570. unsigned ulRec,
  571. LPFILERECEXT lpFR,
  572. unsigned ulSpaces,
  573. PRINTPROC lpfnPrintProc
  574. )
  575. {
  576. int i;
  577. int iRet=0;
  578. if (lpfnPrintProc)
  579. {
  580. iRet += PrintSpaces(vchPrintBuff+iRet, ulSpaces);
  581. iRet += wsprintfA(vchPrintBuff+iRet,"Type=%c Flags=%xh Inode=%0lxh status=%xh 83Name=%ls size=%ld attrib=%lxh \r\n",
  582. lpFR->sFR.uchType, (unsigned)lpFR->sFR.uchFlags, lpFR->sFR.ulidShadow,
  583. lpFR->sFR.uStatus, lpFR->sFR.rgw83Name, lpFR->sFR.ulFileSize, lpFR->sFR.dwFileAttrib);
  584. iRet += PrintSpaces(vchPrintBuff+iRet, ulSpaces);
  585. iRet += wsprintfA(vchPrintBuff+iRet,"time: hi=%lxh lo=%lxh orgtime: hi=%lxh lo=%lxh\r\n"
  586. , lpFR->sFR.ftLastWriteTime.dwHighDateTime,lpFR->sFR.ftLastWriteTime.dwLowDateTime
  587. , lpFR->sFR.ftOrgTime.dwHighDateTime,lpFR->sFR.ftOrgTime.dwLowDateTime);
  588. iRet += PrintSpaces(vchPrintBuff+iRet, ulSpaces);
  589. iRet += wsprintfA(vchPrintBuff+iRet,"Hint: HintFlags=%xh, RefPri=%d, HintPri=%d AliasInode=%0lxh \r\n",
  590. (unsigned)(lpFR->sFR.uchHintFlags)
  591. , (int)(lpFR->sFR.uchRefPri)
  592. , (int)(lpFR->sFR.uchHintPri)
  593. , lpFR->sFR.ulidShadowOrg);
  594. iRet += PrintSpaces(vchPrintBuff+iRet, ulSpaces);
  595. iRet += wsprintfA(vchPrintBuff+iRet,"LFN=%-14ls", lpFR->sFR.rgwName);
  596. for(i = 0; i < OvfCount(lpFR); ++i)
  597. {
  598. iRet += wsprintfA(vchPrintBuff+iRet,"%-74s", &(lpFR->sFR.ulidShadow));
  599. }
  600. iRet += wsprintfA(vchPrintBuff+iRet,"\r\n");
  601. (lpfnPrintProc)(vchPrintBuff);
  602. }
  603. }
  604. int
  605. PrintSpaces(
  606. LPSTR lpBuff,
  607. unsigned ulSpaces
  608. )
  609. {
  610. unsigned i;
  611. int iRet=0;
  612. for (i=0; i< ulSpaces; ++i)
  613. {
  614. iRet += wsprintfA(lpBuff+iRet," ");
  615. }
  616. return iRet;
  617. }