Source code of Windows XP (NT5)
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.

1359 lines
44 KiB

  1. /**************************************************************************************************
  2. FILENAME: BootOptimize.cpp
  3. COPYRIGHT 2001 Microsoft Corporation and Executive Software International, Inc.
  4. DESCRIPTION:
  5. Boot Optimize for NTFS.
  6. **************************************************************************************************/
  7. #include "stdafx.h"
  8. extern "C"{
  9. #include <string.h>
  10. #include <stdio.h>
  11. #include <stdlib.h>
  12. }
  13. #include "Windows.h"
  14. #include <winioctl.h>
  15. #include <math.h>
  16. #include <fcntl.h>
  17. extern "C" {
  18. #include "SysStruc.h"
  19. }
  20. #include "BootOptimizeFat.h"
  21. #include "DfrgCmn.h"
  22. #include "GetReg.h"
  23. #include "defragcommon.h"
  24. #include "Devio.h"
  25. #include "movefile.h"
  26. #include "fssubs.h"
  27. #include "Alloc.h"
  28. //This contains the list of Boot Optimize files that are loaded from the BootOptimize file provided by Microsoft
  29. typedef struct{
  30. TCHAR tBootOptimizeFile[MAX_PATH+1]; //The path to the BootOptimizeFile.
  31. ULONGLONG dBootOptimizeFileLocationLcn; //The location of the file its LCN on disk
  32. LONGLONG dBootOptimizeFileSize; //The size of the file in clusters.
  33. } BOOT_OPTIMIZE_LIST;
  34. HANDLE hBootOptimizeFileList=NULL; //Handle to the memory for the Boot Optimize File list.
  35. BOOT_OPTIMIZE_LIST* pBootOptimizeFileList=NULL; //pointer to the BootOptimize file list
  36. UINT uBootOptimizeCount = 0; //count of how many files read into memory
  37. #if OPTLONGLONGMATH
  38. #define DIVIDELONGLONGBY32(num) Int64ShraMod32((num), 5)
  39. #define MODULUSLONGLONGBY32(num) ((num) & 0x1F)
  40. #else
  41. #define DIVIDELONGLONGBY32(num) ((num) / 32)
  42. #define MODULUSLONGLONGBY32(num) ((num) % 32)
  43. #endif
  44. #define OPTIMAL_LAYOUT_KEY_PATH TEXT("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\OptimalLayout")
  45. #define OPTIMAL_LAYOUT_FILE_VALUE_NAME TEXT("LayoutFilePath")
  46. #define BOOT_OPTIMIZE_REGISTRY_PATH TEXT("SOFTWARE\\Microsoft\\Dfrg\\BootOptimizeFunction")
  47. #define BOOT_OPTIMIZE_ENABLE_FLAG TEXT("Enable")
  48. #define BOOT_OPTIMIZE_REGISTRY_LCNSTARTLOCATION TEXT("LcnStartLocation")
  49. #define BOOT_OPTIMIZE_REGISTRY_LCNENDLOCATION TEXT("LcnEndLocation")
  50. #define BOOT_OPTIMIZE_REGISTRY_COMPLETE TEXT("OptimizeComplete")
  51. #define BOOT_OPTIMIZE_REGISTRY_ERROR TEXT("OptimizeError")
  52. #define BOOT_OPTIMIZE_LAST_WRITTEN_DATETIME TEXT("FileTimeStamp")
  53. /*****************************************************************************************************************
  54. COPYRIGHT 2001 Microsoft Corporation and Executive Software International, Inc.
  55. ROUTINE DESCRIPTION:
  56. The main process of the Boot Optimize functionality
  57. Boot Optimize takes a list of files supplied by Microsoft and puts them in order
  58. on the disk unfragmented. The reason is that these files are what is loaded at
  59. boot time, and since the are contiguous, boot time is reduced 10 to 15 percent.
  60. INPUT:
  61. HANDLE hVolumeHandle Handle to the Volume
  62. LONGLONG BitmapSize The size of the bitmap, since its already known
  63. LONGLONG BytesPerSector The number of Bytes per Sector
  64. LONGLONG TotalClusters Total number of clusters on the drive
  65. BOOL IsNtfs If we are NTFS volume, this is set to TRUE
  66. ULONGLONG MftZoneStart The Cluster Number of the start of the MFT Zone
  67. ULONGLONG MftZoneEnd The Cluster Number of the end of the MFT Zone
  68. TCHAR tDrive The current drive letter
  69. RETURN:
  70. Defrag engine error code.
  71. */
  72. DWORD BootOptimize(
  73. IN HANDLE hVolumeHandle,
  74. IN LONGLONG BitmapSize,
  75. IN LONGLONG BytesPerSector,
  76. IN LONGLONG TotalClusters,
  77. IN BOOL IsNtfs,
  78. IN ULONGLONG MftZoneStart,
  79. IN ULONGLONG MftZoneEnd,
  80. IN TCHAR tDrive
  81. )
  82. {
  83. LONGLONG lFirstAvailableFreeSpace = 0; //the first available free space on the disk dig enough to move to
  84. LONGLONG lLcnStartLocation = 0; //the starting location of where the files were moved last
  85. LONGLONG lLcnEndLocation = 0; //the ending location of where the files were moved last
  86. LONGLONG ulBootOptimizeFileSize = 0; //size in clusters of how big the boot optimize files are
  87. LONGLONG lFreeSpaceSize = 0; //size in clusters of found free space
  88. TCHAR cBootOptimzePath[MAX_PATH]; //string to hold the path of the file
  89. DWORD ErrCode;
  90. //make sure the drive letter is upper case
  91. tDrive = towupper(tDrive);
  92. //check to see if this is the boot volume
  93. if(!IsBootVolume(tDrive)) //was not boot volume so return
  94. {
  95. SaveErrorInRegistry(TEXT("No"),TEXT("Not Boot Volume"));
  96. return ENGERR_BAD_PARAM;
  97. }
  98. //get the registry entries
  99. if(!GetRegistryEntires(cBootOptimzePath))
  100. {
  101. SaveErrorInRegistry(TEXT("No"),TEXT("Missing Registry Entries"));
  102. return ENGERR_BAD_PARAM; //must be some error in getting registry entries
  103. }
  104. //check the date and time stamp of the file
  105. //the starting and ending lcn numbers are reset if the boot optimize input
  106. //file has changed since that last time boot optimize is run. That means that
  107. //the file will go anywhere on the disk where a big enough space exists.
  108. if(CheckDateTimeStampInputFile(cBootOptimzePath))
  109. {
  110. lLcnStartLocation = 0;
  111. lLcnEndLocation = 0;
  112. //save the starting and end lcn numbers in the registry
  113. SetRegistryEntires(lLcnStartLocation, lLcnEndLocation);
  114. } else
  115. {
  116. //if the file has not changed, use the locations out of the registry,
  117. //we will check to see if we can move them forward
  118. lLcnStartLocation = GetStartingEndLncLocations(BOOT_OPTIMIZE_REGISTRY_LCNSTARTLOCATION);
  119. lLcnEndLocation = GetStartingEndLncLocations(BOOT_OPTIMIZE_REGISTRY_LCNENDLOCATION);
  120. }
  121. //check to see if the boot optimize input file exists, if it does not
  122. //save an error in the registry and exit out
  123. if(!OpenReadBootOptimeFileIntoList(cBootOptimzePath, IsNtfs, tDrive))
  124. {
  125. SaveErrorInRegistry(TEXT("No"),TEXT("Can't Open Boot Optimize file"));
  126. return ENGERR_BAD_PARAM;
  127. }
  128. //get the file sizes of the files in the list
  129. ulBootOptimizeFileSize = GetSizeInformationAboutFiles();
  130. //if the size of the files is different than the size of the registry entries
  131. //then set the registry entry settings to zero and it will be as if nothing was
  132. //optimized. I do this because if the file sizes changed, they cannot be in the
  133. //same spots, so optimize them
  134. if(ulBootOptimizeFileSize != (lLcnEndLocation - lLcnStartLocation))
  135. {
  136. lLcnStartLocation = 0;
  137. lLcnEndLocation = 0;
  138. }
  139. //find the first available chunk of free space that is available
  140. for (lFreeSpaceSize = ulBootOptimizeFileSize;
  141. lFreeSpaceSize >= ulBootOptimizeFileSize/8;
  142. lFreeSpaceSize -= ulBootOptimizeFileSize/8) {
  143. lFirstAvailableFreeSpace = FindFreeSpaceChunk(BitmapSize, BytesPerSector,
  144. TotalClusters, lFreeSpaceSize, IsNtfs, MftZoneStart, MftZoneEnd, hVolumeHandle);
  145. //break out with the first found largest free space chunk.
  146. if (0 != lFirstAvailableFreeSpace) {
  147. break;
  148. }
  149. //if the files are already in place, don't continue looking for
  150. //smaller free space chunks.
  151. if (0 != lLcnStartLocation) {
  152. break;
  153. }
  154. }
  155. if (0 == lFirstAvailableFreeSpace) {
  156. //do some clean up and exit out, we don't want to move the files
  157. //free the file list
  158. FreeFileList();
  159. SaveErrorInRegistry(TEXT("No"),TEXT("No free space"));
  160. return ENGERR_LOW_FREESPACE;
  161. }
  162. //check to see if the first available space is before where the files are now
  163. if(lLcnStartLocation != 0) //this is true if we have not successfuly moved the files
  164. {
  165. if(lFirstAvailableFreeSpace > lLcnStartLocation)
  166. {
  167. //do some clean up and exit out, we don't want to move the files
  168. //free the file list
  169. FreeFileList();
  170. SaveErrorInRegistry(TEXT("No"),TEXT("No space before current location"));
  171. return ENGERR_LOW_FREESPACE;
  172. }
  173. }
  174. //move the files to the proper memory location
  175. if(MoveFilesInOrder(lFirstAvailableFreeSpace, lFirstAvailableFreeSpace + lFreeSpaceSize, hVolumeHandle))
  176. {
  177. lLcnStartLocation = lFirstAvailableFreeSpace;
  178. lLcnEndLocation = lLcnStartLocation + lFreeSpaceSize;
  179. SaveErrorInRegistry(TEXT("Yes"),TEXT(" "));
  180. ErrCode = ENG_NOERR;
  181. } else
  182. {
  183. lLcnStartLocation = 0;
  184. lLcnEndLocation = 0;
  185. ErrCode = ENGERR_GENERAL;
  186. }
  187. //save the starting and end lcn numbers in the registry
  188. SetRegistryEntires(lLcnStartLocation, lLcnEndLocation);
  189. //free the file list memory
  190. FreeFileList();
  191. return ErrCode;
  192. }
  193. /*****************************************************************************************************************
  194. COPYRIGHT 2001 Microsoft Corporation and Executive Software International, Inc.
  195. ROUTINE DESCRIPTION:
  196. Reads the boot optimize files from a file and loads then into a list.
  197. INPUT:
  198. FILE *hBootOptimizeFile Handle to the Boot Optimize File
  199. BOOL IsNtfs Boolean if this is a NTFS Volume
  200. TCHAR tDrive TCHAR Drive letter
  201. RETURN:
  202. Returns TRUE if this is boot value, else FALSE if it is not.
  203. */
  204. BOOL OpenReadBootOptimeFileIntoList(
  205. IN TCHAR* cBootOptimzePath,
  206. IN BOOL IsNtfs,
  207. IN TCHAR tDrive
  208. )
  209. {
  210. UINT uNumberofRecords = 0;
  211. //count the number of records in the Boot Optimize File
  212. uNumberofRecords = CountNumberofRecordsinFile(cBootOptimzePath);
  213. //if the number of records is zero, return
  214. if(uNumberofRecords == 0)
  215. {
  216. return FALSE;
  217. }
  218. //create a list of files to optimize, if I run out of space, double the size of the list and
  219. //try again until I either run out of memory or Everything loads correctly.
  220. do
  221. {
  222. //allocate memory for the list of files in boot optimize
  223. //I am allocating for 100 files right now
  224. if (!AllocateMemory((DWORD) (sizeof(BOOT_OPTIMIZE_LIST) * uNumberofRecords), &hBootOptimizeFileList, (PVOID*) &pBootOptimizeFileList))
  225. {
  226. return FALSE;
  227. }
  228. //load the files that need to be optimized into memory
  229. if(!LoadOptimizeFileList(cBootOptimzePath, IsNtfs, tDrive, uNumberofRecords))
  230. {
  231. FreeFileList();
  232. uNumberofRecords = uNumberofRecords * 2;
  233. } else
  234. {
  235. break;
  236. }
  237. } while (1);
  238. return TRUE;
  239. }
  240. /*****************************************************************************************************************
  241. COPYRIGHT 2001 Microsoft Corporation and Executive Software International, Inc.
  242. ROUTINE DESCRIPTION:
  243. Get a rough idea of how many records are in the file and triple it, to make an estimation
  244. of how many files are in the boot optimize file, and I triple it to account for multiple
  245. stream files. Also make the assumption that the file count is atleast 300, so that I can
  246. allocate enough memory to hold all the records.
  247. INPUT:
  248. full path name to the boot optimize file
  249. RETURN:
  250. triple the number of records in the boot optimize file.
  251. */
  252. UINT CountNumberofRecordsinFile(
  253. IN TCHAR* cBootOptimzePath
  254. )
  255. {
  256. UINT uNumberofRecords = 0; //the number of records in the input file
  257. TCHAR tBuffer [MAX_PATH]; //temporary buffer to the input string
  258. ULONG ulLength; //length of the line read in by fgetts
  259. FILE* fBootOptimizeFile; //File Pointer to fBootOptimizeFile
  260. //set read mode to binary
  261. _fmode = _O_BINARY;
  262. //open the file
  263. //if I can't open the file, return a record count of zero
  264. fBootOptimizeFile = _tfopen(cBootOptimzePath,TEXT("r"));
  265. if(fBootOptimizeFile == NULL)
  266. {
  267. return 0;
  268. }
  269. //read the entire file and count the number of records
  270. while(_fgetts(tBuffer,MAX_PATH - 1,fBootOptimizeFile) != 0)
  271. {
  272. // check for terminating carriage return.
  273. ulLength = wcslen(tBuffer);
  274. if (ulLength && (tBuffer[ulLength - 1] == TEXT('\n'))) {
  275. uNumberofRecords++;
  276. }
  277. }
  278. fclose(fBootOptimizeFile);
  279. //triple the number of records we have
  280. if(uNumberofRecords < 100)
  281. {
  282. uNumberofRecords = 100;
  283. }
  284. uNumberofRecords *= 3;
  285. return uNumberofRecords;
  286. }
  287. /*****************************************************************************************************************
  288. COPYRIGHT 2001 Microsoft Corporation and Executive Software International, Inc.
  289. ROUTINE DESCRIPTION:
  290. Reads the boot optimize files from a file and loads then into a list.
  291. INPUT:
  292. FILE *hBootOptimizeFile Handle to the Boot Optimize File
  293. BOOL IsNtfs Boolean if this is a NTFS Volume
  294. TCHAR tDrive TCHAR Drive letter
  295. RETURN:
  296. Returns TRUE if this is boot value, else FALSE if it is not.
  297. */
  298. BOOL LoadOptimizeFileList(
  299. IN TCHAR* cBootOptimzePath,
  300. IN BOOL IsNtfs,
  301. IN TCHAR tDrive,
  302. IN UINT uNumberofRecords
  303. )
  304. {
  305. BY_HANDLE_FILE_INFORMATION FileInformation;
  306. HANDLE hBootOptimizeFileHandle; //temporary Handle to boot optimize files
  307. TCHAR tBuffer [MAX_PATH+1]; //temporary buffer to the input string
  308. ULONG ulLength; //length of the line read in by fgetts
  309. FILE* fBootOptimizeFile; //File Pointer to fBootOptimizeFile
  310. BOOL FileIsDirectory;
  311. //set the number of records read to 0
  312. uBootOptimizeCount = 0;
  313. //set read mode to binary
  314. _fmode = _O_BINARY;
  315. //open the file
  316. fBootOptimizeFile = _tfopen(cBootOptimzePath,TEXT("r"));
  317. if(fBootOptimizeFile != NULL)
  318. {
  319. //read the entire file and check each file to make sure its valid
  320. //then add to the list
  321. while(_fgetts(tBuffer,MAX_PATH,fBootOptimizeFile) != 0)
  322. {
  323. // remove terminating carriage return.
  324. ulLength = wcslen(tBuffer);
  325. if (ulLength < 2) {
  326. continue;
  327. }
  328. if (tBuffer[ulLength - 1] == TEXT('\n')) {
  329. tBuffer[ulLength - 1] = 0;
  330. ulLength--;
  331. if (tBuffer[ulLength - 1] == TEXT('\r')) {
  332. tBuffer[ulLength - 1] = 0;
  333. ulLength--;
  334. }
  335. } else {
  336. continue;
  337. }
  338. if(IsAValidFile(tBuffer, tDrive))
  339. {
  340. hBootOptimizeFileHandle = GetFileHandle(tBuffer);
  341. //if file handle is not NULL, count it, else skip the file, its not a valid file
  342. if(hBootOptimizeFileHandle != NULL)
  343. {
  344. // determine if directory file.
  345. FileIsDirectory = FALSE;
  346. if (GetFileInformationByHandle(hBootOptimizeFileHandle, &FileInformation)) {
  347. FileIsDirectory = (FileInformation.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
  348. }
  349. // on FAT skip directory files: we can't move them.
  350. if (IsNtfs || (!FileIsDirectory)) {
  351. _tcscpy(pBootOptimizeFileList[uBootOptimizeCount].tBootOptimizeFile,tBuffer);
  352. uBootOptimizeCount++;
  353. //check to see if the number of records in the list is greater than the allocated size
  354. //if it is, return FALSE , reallocate the list and try again
  355. if(uBootOptimizeCount >= uNumberofRecords)
  356. {
  357. CloseFileHandle(hBootOptimizeFileHandle);
  358. fclose(fBootOptimizeFile);
  359. return FALSE;
  360. }
  361. if(IsNtfs)
  362. {
  363. //I fail from loading streams also if I run out of space
  364. if(!GetBootOptimizeFileStreams(hBootOptimizeFileHandle, tBuffer, uNumberofRecords))
  365. {
  366. CloseFileHandle(hBootOptimizeFileHandle);
  367. fclose(fBootOptimizeFile);
  368. return FALSE;
  369. }
  370. }
  371. }
  372. CloseFileHandle(hBootOptimizeFileHandle);
  373. }
  374. }
  375. }
  376. //close the file at the end
  377. fclose(fBootOptimizeFile);
  378. }
  379. return TRUE;
  380. }
  381. /*****************************************************************************************************************
  382. COPYRIGHT 2001 Microsoft Corporation and Executive Software International, Inc.
  383. ROUTINE DESCRIPTION:
  384. Validates that we have a valid file name.
  385. INPUT:
  386. TCHAR* phBootOptimizeFileName The file name input from the list
  387. TCHAR tDrive TCHAR Drive letter
  388. RETURN:
  389. Returns TRUE if this is boot value, else FALSE if it is not.
  390. */
  391. BOOL IsAValidFile(
  392. IN TCHAR pBootOptimizeFileName[MAX_PATH+1],
  393. IN TCHAR tDrive
  394. )
  395. {
  396. TCHAR tBootOptimizeFileDrive; //holds the drive letter of the file name input
  397. TCHAR tFileName[MAX_PATH+1];
  398. //ignore blank lines
  399. if(_tcslen(pBootOptimizeFileName) == 0)
  400. {
  401. return FALSE;
  402. }
  403. //set the string to upper case to compare
  404. pBootOptimizeFileName = _tcsupr(pBootOptimizeFileName);
  405. //ignore the group headers
  406. if(_tcsstr(pBootOptimizeFileName,TEXT("[OPTIMALLAYOUTFILE]")) != NULL)
  407. {
  408. return FALSE;
  409. }
  410. //ignore the file = and version = lines
  411. if(_tcsstr(pBootOptimizeFileName,TEXT("VERSION=")) != NULL)
  412. {
  413. return FALSE;
  414. }
  415. //get the drive the file is on, if its not the boot drive, skip the file
  416. tBootOptimizeFileDrive = pBootOptimizeFileName[0];
  417. //convert the characters to upper case before comparison
  418. tBootOptimizeFileDrive = towupper(tBootOptimizeFileDrive);
  419. if(tBootOptimizeFileDrive != tDrive) //files are on boot drive else skip them
  420. {
  421. return FALSE;
  422. }
  423. //get just the file name from the end of the path
  424. if(_tcsrchr(pBootOptimizeFileName,TEXT('\\')) != NULL)
  425. {
  426. _tcscpy(tFileName,_tcsrchr(pBootOptimizeFileName,TEXT('\\'))+1);
  427. } else
  428. {
  429. //not a valid name
  430. return FALSE;
  431. }
  432. //if string length is zero, must be directory, return
  433. if(_tcslen(tFileName) == 0)
  434. {
  435. return TRUE;
  436. }
  437. if(_tcsicmp(tFileName,TEXT("BOOTSECT.DOS")) == 0)
  438. {
  439. return FALSE;
  440. }
  441. if(_tcsicmp(tFileName,TEXT("SAFEBOOT.FS")) == 0)
  442. {
  443. return FALSE;
  444. }
  445. if(_tcsicmp(tFileName,TEXT("SAFEBOOT.CSV")) == 0)
  446. {
  447. return FALSE;
  448. }
  449. if(_tcsicmp(tFileName,TEXT("SAFEBOOT.RSV")) == 0)
  450. {
  451. return FALSE;
  452. }
  453. if(_tcsicmp(tFileName,TEXT("HIBERFIL.SYS")) == 0)
  454. {
  455. return FALSE;
  456. }
  457. if(_tcsicmp(tFileName,TEXT("MEMORY.DMP")) == 0)
  458. {
  459. return FALSE;
  460. }
  461. if(_tcsicmp(tFileName,TEXT("PAGEFILE.SYS")) == 0)
  462. {
  463. return FALSE;
  464. }
  465. //file is OK, return TRUE
  466. return TRUE;
  467. }
  468. /*****************************************************************************************************************
  469. COPYRIGHT 2001 Microsoft Corporation and Executive Software International, Inc.
  470. ROUTINE DESCRIPTION:
  471. get the file handle for the files in the list
  472. INPUT:
  473. TCHAR* tBootOptimizeFile TCHAR* to the boot optimize file name
  474. RETURN:
  475. HANDLE to the file or NULL
  476. */
  477. HANDLE GetFileHandle(
  478. IN TCHAR* tBootOptimizeFile
  479. )
  480. {
  481. HANDLE hFile = NULL; //HANDLE for the file to open, it is returned if is valid
  482. DWORD dwCreationDisposition;
  483. //sks bug #217428 change the parameters so that all files are moved
  484. dwCreationDisposition = FILE_FLAG_BACKUP_SEMANTICS | FILE_OPEN_REPARSE_POINT;
  485. hFile = CreateFile(tBootOptimizeFile,
  486. FILE_READ_ATTRIBUTES | SYNCHRONIZE,
  487. 0,
  488. NULL,
  489. OPEN_EXISTING,
  490. dwCreationDisposition,
  491. NULL);
  492. if (hFile == INVALID_HANDLE_VALUE)
  493. {
  494. return NULL;
  495. } else
  496. {
  497. return hFile;
  498. }
  499. }
  500. /*****************************************************************************************************************
  501. COPYRIGHT 2001 Microsoft Corporation and Executive Software International, Inc.
  502. ROUTINE DESCRIPTION:
  503. get the file handle for the files in the list
  504. INPUT:
  505. TCHAR* tBootOptimizeFile TCHAR* to the boot optimize file name
  506. RETURN:
  507. HANDLE to the file or NULL
  508. */
  509. BOOL CloseFileHandle(
  510. IN HANDLE hBootOptimizeFileHandle
  511. )
  512. {
  513. if(hBootOptimizeFileHandle != NULL)
  514. {
  515. CloseHandle(hBootOptimizeFileHandle);
  516. return TRUE;
  517. }
  518. return FALSE;
  519. }
  520. /*****************************************************************************************************************
  521. COPYRIGHT 2001 Microsoft Corporation and Executive Software International, Inc.
  522. ROUTINE DESCRIPTION:
  523. Gets all the streams for a file in NTFS and adds them to the file list, should not be called for FAT/FAT32
  524. GLOBALS:
  525. uBootOptimizeCount
  526. pBootOptimizeFileList
  527. INPUT:
  528. HANDLE hBootOptimizeFileHandle HANDLE to the Boot Optimize File
  529. RETURN:
  530. TRUE is all streams of all files was loaded correctly.
  531. FALSE is an error occured loading all the streams
  532. */
  533. BOOL GetBootOptimizeFileStreams(
  534. IN HANDLE hBootOptimizeFileHandle,
  535. IN TCHAR* tBootOptimizeFile,
  536. IN UINT uNumberofRecords
  537. )
  538. {
  539. HANDLE htempBootOptimizeFileHandle; //temporary Handle to the file
  540. // fixed size buffer for some API calls
  541. const ULONG FixedBufferSize = 4096; //fixed buffer size for API Calls
  542. static UCHAR FixedBuffer[FixedBufferSize]; //fixed buffer for API Calls
  543. // clear buffer
  544. memset(FixedBuffer, 0, FixedBufferSize);
  545. // query file system
  546. NTSTATUS status; //NTSTATUS
  547. IO_STATUS_BLOCK iosb; //IO status block
  548. FILE_STREAM_INFORMATION* StreamInfo = (FILE_STREAM_INFORMATION*) FixedBuffer; //stream info
  549. FILE_STREAM_INFORMATION* Stream; //info on the stream
  550. PTCHAR StreamName = NULL; //TCHAR pointer to the stream name
  551. TCHAR tTemporaryStreamName[MAX_PATH+1]; //the name of the stream filename:stream
  552. if (hBootOptimizeFileHandle == INVALID_HANDLE_VALUE)
  553. {
  554. return TRUE;
  555. } else
  556. {
  557. status = NtQueryInformationFile(hBootOptimizeFileHandle,
  558. &iosb,
  559. FixedBuffer,
  560. FixedBufferSize,
  561. FileStreamInformation);
  562. // if this buffer was too small return
  563. if (status == STATUS_BUFFER_OVERFLOW || status == STATUS_BUFFER_TOO_SMALL)
  564. {
  565. return TRUE;
  566. }
  567. // process the return data
  568. else
  569. {
  570. Stream = StreamInfo;
  571. // loop thru returned data
  572. while (Stream != NULL)
  573. {
  574. // handle stream name
  575. if (Stream->StreamNameLength > 0)
  576. {
  577. // parse it
  578. StreamName = ParseStreamName((PTCHAR) &Stream->StreamName);
  579. if (StreamName != NULL)
  580. {
  581. //
  582. // Make sure we don't go past MAX_PATH - 1, since our global array can only handle
  583. // stuff up to MAX_PATH + 1 characters long (don't forget the ":" and terminating
  584. // NULL)
  585. //
  586. if ((wcslen(tBootOptimizeFile) + wcslen(StreamName)) < (MAX_PATH-1)) {
  587. //set the name of the file in the list and get a handle for the file
  588. //append the stream name to the file name
  589. _tcscpy(tTemporaryStreamName,tBootOptimizeFile);
  590. _tcscat(tTemporaryStreamName,TEXT(":"));
  591. _tcscat(tTemporaryStreamName,StreamName);
  592. htempBootOptimizeFileHandle = GetFileHandle(tTemporaryStreamName);
  593. if(htempBootOptimizeFileHandle != NULL)
  594. {
  595. _tcscpy(pBootOptimizeFileList[uBootOptimizeCount].tBootOptimizeFile,tTemporaryStreamName);
  596. CloseFileHandle(htempBootOptimizeFileHandle);
  597. uBootOptimizeCount++;
  598. if(uBootOptimizeCount >= uNumberofRecords)
  599. {
  600. return FALSE;
  601. }
  602. }
  603. }
  604. }
  605. }
  606. // move on to next one or break
  607. if (Stream->NextEntryOffset > 0)
  608. {
  609. Stream = (FILE_STREAM_INFORMATION *) (((UCHAR*) Stream) + Stream->NextEntryOffset);
  610. }
  611. else
  612. {
  613. Stream = NULL;
  614. }
  615. }
  616. }
  617. }
  618. return TRUE;
  619. }
  620. /*****************************************************************************************************************
  621. COPYRIGHT 2001 Microsoft Corporation and Executive Software International, Inc.
  622. ROUTINE DESCRIPTION:
  623. parses string name for streams
  624. INPUT:
  625. PTCHAR StreamName The stream name to parse
  626. RETURN:
  627. PTCHAR StreamName The parsed stream name
  628. */
  629. static PTCHAR ParseStreamName(
  630. IN OUT PTCHAR StreamName
  631. )
  632. {
  633. // nothing in, nothing out
  634. if (StreamName == NULL || _tcslen(StreamName) < 1)
  635. {
  636. return NULL;
  637. }
  638. // ignore the default stream
  639. if (_tcscmp(StreamName, TEXT("::$DATA")) == MATCH)
  640. {
  641. return NULL;
  642. }
  643. // parse it
  644. PTCHAR TmpStreamName = StreamName;
  645. // skip leading colons
  646. while (TmpStreamName[0] == TEXT(':'))
  647. {
  648. TmpStreamName++;
  649. }
  650. // take out ":$DATA"
  651. PTCHAR ptr = _tcsstr(TmpStreamName, TEXT(":$DATA"));
  652. if (ptr != NULL)
  653. {
  654. ptr[0] = TEXT('\0');
  655. }
  656. // see if we have anything left
  657. if (_tcslen(TmpStreamName) < 1)
  658. {
  659. return NULL;
  660. }
  661. return TmpStreamName;
  662. }
  663. /*****************************************************************************************************************
  664. COPYRIGHT 2001 Microsoft Corporation and Executive Software International, Inc.
  665. ROUTINE DESCRIPTION:
  666. Frees the memory allocated for the boot optimize files.
  667. INPUT:
  668. None
  669. RETURN:
  670. None
  671. */
  672. VOID FreeFileList()
  673. {
  674. if(hBootOptimizeFileList != NULL)
  675. {
  676. while (GlobalUnlock(hBootOptimizeFileList))
  677. {
  678. ;
  679. }
  680. GlobalFree(hBootOptimizeFileList);
  681. hBootOptimizeFileList = NULL;
  682. }
  683. return;
  684. }
  685. /*****************************************************************************************************************
  686. COPYRIGHT 2001 Microsoft Corporation and Executive Software International, Inc.
  687. ROUTINE DESCRIPTION:
  688. Get the size of each file in the list of the Boot Optimize Files.
  689. INPUT:
  690. None
  691. RETURN:
  692. None
  693. */
  694. LONGLONG GetSizeInformationAboutFiles()
  695. {
  696. HANDLE hBootOptimizeFileHandle; //Handle to the boot optimize file
  697. LONGLONG ulBootOptimizeFileSize = 0; //store the total size of the files
  698. //get the size of each file in the list
  699. for(UINT ii=0;ii<uBootOptimizeCount;ii++)
  700. {
  701. //get the handle to the file
  702. hBootOptimizeFileHandle = GetFileHandle(pBootOptimizeFileList[ii].tBootOptimizeFile);
  703. if(hBootOptimizeFileHandle != NULL)
  704. {
  705. //set the size of the file in the list
  706. pBootOptimizeFileList[ii].dBootOptimizeFileSize = GetFileSizeInfo(hBootOptimizeFileHandle);
  707. //sum up the sizes of the files
  708. ulBootOptimizeFileSize += pBootOptimizeFileList[ii].dBootOptimizeFileSize;
  709. CloseFileHandle(hBootOptimizeFileHandle);
  710. } else
  711. {
  712. //can't open the file, so set the size to zero.
  713. pBootOptimizeFileList[ii].dBootOptimizeFileSize = 0;
  714. }
  715. }
  716. return ulBootOptimizeFileSize;
  717. }
  718. /*****************************************************************************************************************
  719. COPYRIGHT 2001 Microsoft Corporation and Executive Software International, Inc.
  720. ROUTINE DESCRIPTION:
  721. Get the size of the file in clusters from calling FSCL_GET_RETRIEVAL_POINTERS.
  722. INPUT:
  723. HANDLE hBootOptimizeFileHandle The handle to the File to get the size of
  724. RETURN:
  725. The size of the file in clusters
  726. */
  727. ULONGLONG GetFileSizeInfo(
  728. IN HANDLE hBootOptimizeFileHandle
  729. )
  730. {
  731. ULONGLONG ulSizeofFileInClusters = 0; //size of the file in clusters
  732. int i;
  733. ULONGLONG startVcn = 0; //starting VCN of the file, always 0
  734. STARTING_VCN_INPUT_BUFFER startingVcn; //starting VCN Buffer
  735. ULONG BytesReturned = 0; //number of bytes returned by ESDeviceIoControl
  736. HANDLE hRetrievalPointersBuffer = NULL; //Handle to the Retrieval Pointers Buffer
  737. PRETRIEVAL_POINTERS_BUFFER pRetrievalPointersBuffer = NULL; //pointer to the Retrieval Pointer
  738. PLARGE_INTEGER pRetrievalPointers = NULL; //Pointer to retrieval pointers
  739. ULONG RetrievalPointers = 0x100; //Number of extents for the file, try 256 first
  740. BOOL bGetRetrievalPointersMore = TRUE; //boolean to test the end of getting retrieval pointers
  741. if (hBootOptimizeFileHandle == INVALID_HANDLE_VALUE)
  742. {
  743. return 0;
  744. }
  745. //zero the memory of the starting VCN input buffer
  746. ZeroMemory(&startVcn, sizeof(STARTING_VCN_INPUT_BUFFER));
  747. //0.1E00 Read the retrieval pointers into a buffer in memory.
  748. while(bGetRetrievalPointersMore){
  749. //0.0E00 Allocate a RetrievalPointersBuffer.
  750. if(!AllocateMemory(sizeof(RETRIEVAL_POINTERS_BUFFER) + (RetrievalPointers * 2 * sizeof(LARGE_INTEGER)),
  751. &hRetrievalPointersBuffer,
  752. (void**)(PCHAR*)&pRetrievalPointersBuffer))
  753. {
  754. return 0;
  755. }
  756. startingVcn.StartingVcn.QuadPart = 0;
  757. if(ESDeviceIoControl(hBootOptimizeFileHandle,
  758. FSCTL_GET_RETRIEVAL_POINTERS,
  759. &startingVcn,
  760. sizeof(STARTING_VCN_INPUT_BUFFER),
  761. pRetrievalPointersBuffer,
  762. (DWORD)GlobalSize(hRetrievalPointersBuffer),
  763. &BytesReturned,
  764. NULL))
  765. {
  766. bGetRetrievalPointersMore = FALSE;
  767. } else
  768. {
  769. //This occurs on a zero length file (no clusters allocated).
  770. if(GetLastError() == ERROR_HANDLE_EOF)
  771. {
  772. //file is zero lenght, so return 0
  773. //free the memory for the retrival pointers
  774. //the while loop makes sure all occurances are unlocked
  775. while (GlobalUnlock(hRetrievalPointersBuffer))
  776. {
  777. ;
  778. }
  779. GlobalFree(hRetrievalPointersBuffer);
  780. hRetrievalPointersBuffer = NULL;
  781. return 0;
  782. }
  783. //0.0E00 Check to see if the error is not because the buffer is too small.
  784. if(GetLastError() == ERROR_MORE_DATA)
  785. {
  786. //0.1E00 Double the buffer size until it's large enough to hold the file's extent list.
  787. RetrievalPointers *= 2;
  788. } else
  789. {
  790. //some other error, return 0
  791. //free the memory for the retrival pointers
  792. //the while loop makes sure all occurances are unlocked
  793. while (GlobalUnlock(hRetrievalPointersBuffer))
  794. {
  795. ;
  796. }
  797. GlobalFree(hRetrievalPointersBuffer);
  798. hRetrievalPointersBuffer = NULL;
  799. return 0;
  800. }
  801. }
  802. }
  803. //loop through the retrival pointer list and add up the size of the file
  804. startVcn = pRetrievalPointersBuffer->StartingVcn.QuadPart;
  805. for (i = 0; i < (ULONGLONG) pRetrievalPointersBuffer->ExtentCount; i++)
  806. {
  807. ulSizeofFileInClusters += pRetrievalPointersBuffer->Extents[i].NextVcn.QuadPart - startVcn;
  808. startVcn = pRetrievalPointersBuffer->Extents[i].NextVcn.QuadPart;
  809. }
  810. if(hRetrievalPointersBuffer != NULL)
  811. {
  812. //free the memory for the retrival pointers
  813. //the while loop makes sure all occurances are unlocked
  814. while (GlobalUnlock(hRetrievalPointersBuffer))
  815. {
  816. ;
  817. }
  818. GlobalFree(hRetrievalPointersBuffer);
  819. hRetrievalPointersBuffer = NULL;
  820. }
  821. return ulSizeofFileInClusters;
  822. }
  823. /*****************************************************************************************************************
  824. COPYRIGHT 2001 Microsoft Corporation and Executive Software International, Inc.
  825. ROUTINE DESCRIPTION:
  826. Loops through the file list and moves the files
  827. INPUT:
  828. ULONGLONG lMoveFileHere Cluster to move the files to
  829. ULONGLONG lEndOfFreeSpace Cluster where free space chunk ends.
  830. RETURN:
  831. BOOL if the move completed, returns TRUE, else if a move failed, returns FALSE
  832. */
  833. BOOL MoveFilesInOrder(
  834. IN ULONGLONG lMoveFileHere,
  835. IN ULONGLONG lEndOfFreeSpace,
  836. IN HANDLE hBootVolumeHandle
  837. )
  838. {
  839. HANDLE hBootOptimizeFileHandle; //Handle for the boot optimize file
  840. for(int ii=0;ii<(int)uBootOptimizeCount;ii++)
  841. {
  842. hBootOptimizeFileHandle = GetFileHandle(pBootOptimizeFileList[ii].tBootOptimizeFile);
  843. if(hBootOptimizeFileHandle != NULL)
  844. {
  845. // Try to move the files in the list into contiguous space
  846. MoveFileLocation(hBootOptimizeFileHandle,
  847. lMoveFileHere,
  848. pBootOptimizeFileList[ii].dBootOptimizeFileSize,
  849. 0,
  850. hBootVolumeHandle);
  851. CloseFileHandle(hBootOptimizeFileHandle);
  852. }
  853. //increase the move to location by the size of the file
  854. lMoveFileHere += pBootOptimizeFileList[ii].dBootOptimizeFileSize;
  855. //have we gone past the end of free space chunk?
  856. if (lMoveFileHere > lEndOfFreeSpace) {
  857. break;
  858. }
  859. }
  860. return TRUE;
  861. }
  862. /*****************************************************************************************************************
  863. COPYRIGHT 2001 Microsoft Corporation and Executive Software International, Inc.
  864. ROUTINE DESCRIPTION:
  865. Gets the registry entries at the beginning of the program
  866. INPUT:
  867. Success - TRUE
  868. Failed - FALSE
  869. RETURN:
  870. None
  871. */
  872. BOOL GetRegistryEntires(
  873. OUT TCHAR cBootOptimzePath[MAX_PATH]
  874. )
  875. {
  876. HKEY hValue = NULL; //hkey for the registry value
  877. DWORD dwRegValueSize = 0; //size of the registry value string
  878. long ret = 0; //return value from SetRegValue
  879. TCHAR cEnabledString[2]; //holds the enabled flag
  880. // get Boot Optimize file name from registry
  881. dwRegValueSize = sizeof(cEnabledString);
  882. ret = GetRegValue(
  883. &hValue,
  884. BOOT_OPTIMIZE_REGISTRY_PATH,
  885. BOOT_OPTIMIZE_ENABLE_FLAG,
  886. cEnabledString,
  887. &dwRegValueSize);
  888. RegCloseKey(hValue);
  889. //check to see if the key exists, else exit from routine
  890. if (ret != ERROR_SUCCESS)
  891. {
  892. return FALSE;
  893. }
  894. //check to see that boot optimize is enabled
  895. if(cEnabledString[0] != TEXT('Y'))
  896. {
  897. return FALSE;
  898. }
  899. // get Boot Optimize file name from registry
  900. hValue = NULL;
  901. dwRegValueSize = MAX_PATH;
  902. ret = GetRegValue(
  903. &hValue,
  904. OPTIMAL_LAYOUT_KEY_PATH,
  905. OPTIMAL_LAYOUT_FILE_VALUE_NAME,
  906. cBootOptimzePath,
  907. &dwRegValueSize);
  908. RegCloseKey(hValue);
  909. //check to see if the key exists, else exit from routine
  910. if (ret != ERROR_SUCCESS)
  911. {
  912. return FALSE;
  913. }
  914. return TRUE;
  915. }
  916. /*****************************************************************************************************************
  917. COPYRIGHT 2001 Microsoft Corporation and Executive Software International, Inc.
  918. ROUTINE DESCRIPTION:
  919. Gets the registry entries at the beginning of the program
  920. INPUT:
  921. pointer to the registry key
  922. RETURN:
  923. Success - TRUE
  924. Failed - FALSE
  925. */
  926. LONGLONG GetStartingEndLncLocations(
  927. IN PTCHAR pRegKey
  928. )
  929. {
  930. HKEY hValue = NULL; //hkey for the registry value
  931. DWORD dwRegValueSize = 0; //size of the registry value string
  932. long ret = 0; //return value from SetRegValue
  933. TCHAR cRegValue[100]; //string to hold the value for the registry
  934. LONGLONG lLcnStartEndLocation = 0;
  935. //get the LcnStartLocation from the registry
  936. dwRegValueSize = sizeof(cRegValue);
  937. ret = GetRegValue(
  938. &hValue,
  939. BOOT_OPTIMIZE_REGISTRY_PATH,
  940. pRegKey,
  941. cRegValue,
  942. &dwRegValueSize);
  943. RegCloseKey(hValue);
  944. //check to see if the key exists, else exit from routine
  945. if (ret != ERROR_SUCCESS)
  946. {
  947. hValue = NULL;
  948. _stprintf(cRegValue,TEXT("%d"),0);
  949. //add the LcnStartLocation to the registry
  950. dwRegValueSize = sizeof(cRegValue);
  951. ret = SetRegValue(
  952. &hValue,
  953. BOOT_OPTIMIZE_REGISTRY_PATH,
  954. pRegKey,
  955. cRegValue,
  956. dwRegValueSize,
  957. REG_SZ);
  958. RegCloseKey(hValue);
  959. } else
  960. {
  961. lLcnStartEndLocation = _ttoi(cRegValue);
  962. }
  963. return lLcnStartEndLocation;
  964. }
  965. /*****************************************************************************************************************
  966. COPYRIGHT 2001 Microsoft Corporation and Executive Software International, Inc.
  967. ROUTINE DESCRIPTION:
  968. Set the registry entries at the end
  969. INPUT:
  970. None
  971. RETURN:
  972. None
  973. */
  974. VOID SetRegistryEntires(
  975. IN LONGLONG lLcnStartLocation,
  976. IN LONGLONG lLcnEndLocation
  977. )
  978. {
  979. HKEY hValue = NULL; //hkey for the registry value
  980. DWORD dwRegValueSize = 0; //size of the registry value string
  981. long ret = 0; //return value from SetRegValue
  982. TCHAR cRegValue[100]; //string to hold the value for the registry
  983. _stprintf(cRegValue,TEXT("%I64d"),lLcnStartLocation);
  984. //set the LcnEndLocation from the registry
  985. dwRegValueSize = sizeof(cRegValue);
  986. ret = SetRegValue(
  987. &hValue,
  988. BOOT_OPTIMIZE_REGISTRY_PATH,
  989. BOOT_OPTIMIZE_REGISTRY_LCNSTARTLOCATION,
  990. cRegValue,
  991. dwRegValueSize,
  992. REG_SZ);
  993. RegCloseKey(hValue);
  994. hValue = NULL;
  995. _stprintf(cRegValue,TEXT("%I64d"),lLcnEndLocation);
  996. //set the LcnEndLocation from the registry
  997. dwRegValueSize = sizeof(cRegValue);
  998. ret = SetRegValue(
  999. &hValue,
  1000. BOOT_OPTIMIZE_REGISTRY_PATH,
  1001. BOOT_OPTIMIZE_REGISTRY_LCNENDLOCATION,
  1002. cRegValue,
  1003. dwRegValueSize,
  1004. REG_SZ);
  1005. RegCloseKey(hValue);
  1006. }
  1007. /*****************************************************************************************************************
  1008. COPYRIGHT 2001 Microsoft Corporation and Executive Software International, Inc.
  1009. ROUTINE DESCRIPTION:
  1010. Save the error that may have occured in the registry
  1011. INPUT:
  1012. TCHAR tComplete Set to Y when everything worked, set to N when error
  1013. TCHAR* tErrorString A description of what error occured.
  1014. RETURN:
  1015. None
  1016. */
  1017. VOID SaveErrorInRegistry(
  1018. TCHAR* tComplete,
  1019. TCHAR* tErrorString)
  1020. {
  1021. HKEY hValue = NULL; //hkey for the registry value
  1022. DWORD dwRegValueSize = 0; //size of the registry value string
  1023. long ret = 0; //return value from SetRegValue
  1024. //set the error code of the error in the registry
  1025. dwRegValueSize = 2*(_tcslen(tErrorString));
  1026. ret = SetRegValue(
  1027. &hValue,
  1028. BOOT_OPTIMIZE_REGISTRY_PATH,
  1029. BOOT_OPTIMIZE_REGISTRY_ERROR,
  1030. tErrorString,
  1031. dwRegValueSize,
  1032. REG_SZ);
  1033. RegCloseKey(hValue);
  1034. //set the error status in the registry
  1035. hValue = NULL;
  1036. dwRegValueSize = 2*(_tcslen(tComplete));
  1037. ret = SetRegValue(
  1038. &hValue,
  1039. BOOT_OPTIMIZE_REGISTRY_PATH,
  1040. BOOT_OPTIMIZE_REGISTRY_COMPLETE,
  1041. tComplete,
  1042. dwRegValueSize,
  1043. REG_SZ);
  1044. RegCloseKey(hValue);
  1045. }
  1046. /*****************************************************************************************************************
  1047. COPYRIGHT 2001 Microsoft Corporation and Executive Software International, Inc.
  1048. ROUTINE DESCRIPTION:
  1049. Get the date/time stamp of the input file
  1050. INPUT:
  1051. full path to the boot optimize file
  1052. RETURN:
  1053. TRUE if file time does not match what is in the registry
  1054. FALSE if the file time matches what is in the registry
  1055. */
  1056. BOOL CheckDateTimeStampInputFile(
  1057. IN TCHAR cBootOptimzePath[MAX_PATH]
  1058. )
  1059. {
  1060. WIN32_FILE_ATTRIBUTE_DATA extendedAttr; //structure to hold file attributes
  1061. LARGE_INTEGER tBootOptimeFileTime; //holds the last write time of the file
  1062. LARGE_INTEGER tBootOptimeRegistryFileTime; //holds the last write time of the file from registry
  1063. HKEY hValue = NULL; //hkey for the registry value
  1064. DWORD dwRegValueSize = 0; //size of the registry value string
  1065. long ret = 0; //return value from SetRegValue
  1066. tBootOptimeFileTime.LowPart = 0;
  1067. tBootOptimeFileTime.HighPart = 0;
  1068. tBootOptimeRegistryFileTime.LowPart = 0;
  1069. tBootOptimeRegistryFileTime.HighPart = 0;
  1070. //get the last write time of the file
  1071. //if it fails, return FALSE
  1072. if (GetFileAttributesEx (cBootOptimzePath,
  1073. GetFileExInfoStandard,
  1074. &extendedAttr))
  1075. {
  1076. tBootOptimeFileTime.LowPart = extendedAttr.ftLastWriteTime.dwLowDateTime;
  1077. tBootOptimeFileTime.HighPart = extendedAttr.ftLastWriteTime.dwHighDateTime;
  1078. } else
  1079. {
  1080. return TRUE; //some error happened and we exit and say we cant get the file time
  1081. }
  1082. //get the time from the registry
  1083. hValue = NULL;
  1084. dwRegValueSize = sizeof(tBootOptimeFileTime.QuadPart);
  1085. ret = GetRegValue(
  1086. &hValue,
  1087. BOOT_OPTIMIZE_REGISTRY_PATH,
  1088. BOOT_OPTIMIZE_LAST_WRITTEN_DATETIME,
  1089. &(LONGLONG)tBootOptimeRegistryFileTime.QuadPart,
  1090. &dwRegValueSize);
  1091. RegCloseKey(hValue);
  1092. //check to see if the key exists, if it does, check to see if the date/time stamp
  1093. //matches, if it does, exit else write a registry entry
  1094. if (ret == ERROR_SUCCESS)
  1095. {
  1096. if(tBootOptimeFileTime.QuadPart == tBootOptimeRegistryFileTime.QuadPart)
  1097. {
  1098. return FALSE; //the file times matched and we exit
  1099. }
  1100. }
  1101. hValue = NULL;
  1102. //update the date and time of the bootoptimize file to the registry
  1103. dwRegValueSize = sizeof(tBootOptimeFileTime.QuadPart);
  1104. ret = SetRegValue(
  1105. &hValue,
  1106. BOOT_OPTIMIZE_REGISTRY_PATH,
  1107. BOOT_OPTIMIZE_LAST_WRITTEN_DATETIME,
  1108. (LONGLONG)tBootOptimeFileTime.QuadPart,
  1109. dwRegValueSize,
  1110. REG_QWORD);
  1111. RegCloseKey(hValue);
  1112. return TRUE;
  1113. }