1 /* 2 * PROJECT: ReactOS Setup Library 3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) 4 * PURPOSE: Filesystem Format and ChkDsk support functions 5 * COPYRIGHT: Copyright 2003-2019 Casper S. Hornstrup <chorns@users.sourceforge.net> 6 * Copyright 2017-2024 Hermès Bélusca-Maïto <hermes.belusca-maito@reactos.org> 7 */ 8 9 // 10 // See also: https://git.reactos.org/?p=reactos.git;a=blob;f=reactos/dll/win32/fmifs/init.c;h=e895f5ef9cae4806123f6bbdd3dfed37ec1c8d33;hb=b9db9a4e377a2055f635b2fb69fef4e1750d219c 11 // for how to get FS providers in a dynamic way. In the (near) future we may 12 // consider merging some of this code with us into a fmifs / fsutil / fslib library... 13 // 14 15 /* INCLUDES *****************************************************************/ 16 17 #include "precomp.h" 18 19 #include "partlist.h" 20 #include "fsrec.h" 21 #include "bootcode.h" 22 #include "fsutil.h" 23 24 #include <fslib/vfatlib.h> 25 #include <fslib/btrfslib.h> 26 // #include <fslib/ext2lib.h> 27 // #include <fslib/ntfslib.h> 28 29 #define NDEBUG 30 #include <debug.h> 31 32 33 /* TYPEDEFS *****************************************************************/ 34 35 #include <pshpack1.h> 36 typedef struct _FAT_BOOTSECTOR 37 { 38 UCHAR JumpBoot[3]; // Jump instruction to boot code 39 CHAR OemName[8]; // "MSWIN4.1" for MS formatted volumes 40 USHORT BytesPerSector; // Bytes per sector 41 UCHAR SectorsPerCluster; // Number of sectors in a cluster 42 USHORT ReservedSectors; // Reserved sectors, usually 1 (the bootsector) 43 UCHAR NumberOfFats; // Number of FAT tables 44 USHORT RootDirEntries; // Number of root directory entries (fat12/16) 45 USHORT TotalSectors; // Number of total sectors on the drive, 16-bit 46 UCHAR MediaDescriptor; // Media descriptor byte 47 USHORT SectorsPerFat; // Sectors per FAT table (fat12/16) 48 USHORT SectorsPerTrack; // Number of sectors in a track 49 USHORT NumberOfHeads; // Number of heads on the disk 50 ULONG HiddenSectors; // Hidden sectors (sectors before the partition start like the partition table) 51 ULONG TotalSectorsBig; // This field is the new 32-bit total count of sectors on the volume 52 UCHAR DriveNumber; // Int 0x13 drive number (e.g. 0x80) 53 UCHAR Reserved1; // Reserved (used by Windows NT). Code that formats FAT volumes should always set this byte to 0. 54 UCHAR BootSignature; // Extended boot signature (0x29). This is a signature byte that indicates that the following three fields in the boot sector are present. 55 ULONG VolumeSerialNumber; // Volume serial number 56 CHAR VolumeLabel[11]; // Volume label. This field matches the 11-byte volume label recorded in the root directory 57 CHAR FileSystemType[8]; // One of the strings "FAT12 ", "FAT16 ", or "FAT " 58 59 UCHAR BootCodeAndData[448]; // The remainder of the boot sector 60 61 USHORT BootSectorMagic; // 0xAA55 62 63 } FAT_BOOTSECTOR, *PFAT_BOOTSECTOR; 64 C_ASSERT(sizeof(FAT_BOOTSECTOR) == FAT_BOOTSECTOR_SIZE); 65 66 typedef struct _FAT32_BOOTSECTOR 67 { 68 UCHAR JumpBoot[3]; // Jump instruction to boot code 69 CHAR OemName[8]; // "MSWIN4.1" for MS formatted volumes 70 USHORT BytesPerSector; // Bytes per sector 71 UCHAR SectorsPerCluster; // Number of sectors in a cluster 72 USHORT ReservedSectors; // Reserved sectors, usually 1 (the bootsector) 73 UCHAR NumberOfFats; // Number of FAT tables 74 USHORT RootDirEntries; // Number of root directory entries (fat12/16) 75 USHORT TotalSectors; // Number of total sectors on the drive, 16-bit 76 UCHAR MediaDescriptor; // Media descriptor byte 77 USHORT SectorsPerFat; // Sectors per FAT table (fat12/16) 78 USHORT SectorsPerTrack; // Number of sectors in a track 79 USHORT NumberOfHeads; // Number of heads on the disk 80 ULONG HiddenSectors; // Hidden sectors (sectors before the partition start like the partition table) 81 ULONG TotalSectorsBig; // This field is the new 32-bit total count of sectors on the volume 82 ULONG SectorsPerFatBig; // This field is the FAT32 32-bit count of sectors occupied by ONE FAT. BPB_FATSz16 must be 0 83 USHORT ExtendedFlags; // Extended flags (fat32) 84 USHORT FileSystemVersion; // File system version (fat32) 85 ULONG RootDirStartCluster; // Starting cluster of the root directory (fat32) 86 USHORT FsInfo; // Sector number of FSINFO structure in the reserved area of the FAT32 volume. Usually 1. 87 USHORT BackupBootSector; // If non-zero, indicates the sector number in the reserved area of the volume of a copy of the boot record. Usually 6. 88 UCHAR Reserved[12]; // Reserved for future expansion 89 UCHAR DriveNumber; // Int 0x13 drive number (e.g. 0x80) 90 UCHAR Reserved1; // Reserved (used by Windows NT). Code that formats FAT volumes should always set this byte to 0. 91 UCHAR BootSignature; // Extended boot signature (0x29). This is a signature byte that indicates that the following three fields in the boot sector are present. 92 ULONG VolumeSerialNumber; // Volume serial number 93 CHAR VolumeLabel[11]; // Volume label. This field matches the 11-byte volume label recorded in the root directory 94 CHAR FileSystemType[8]; // Always set to the string "FAT32 " 95 96 UCHAR BootCodeAndData[420]; // The remainder of the boot sector 97 98 USHORT BootSectorMagic; // 0xAA55 99 100 } FAT32_BOOTSECTOR, *PFAT32_BOOTSECTOR; 101 C_ASSERT(sizeof(FAT32_BOOTSECTOR) == FAT32_BOOTSECTOR_SIZE); 102 103 typedef struct _BTRFS_BOOTSECTOR 104 { 105 UCHAR JumpBoot[3]; 106 UCHAR ChunkMapSize; 107 UCHAR BootDrive; 108 ULONGLONG PartitionStartLBA; 109 UCHAR Fill[1521]; // 1536 - 15 110 USHORT BootSectorMagic; 111 } BTRFS_BOOTSECTOR, *PBTRFS_BOOTSECTOR; 112 C_ASSERT(sizeof(BTRFS_BOOTSECTOR) == BTRFS_BOOTSECTOR_SIZE); 113 114 typedef struct _NTFS_BOOTSECTOR 115 { 116 UCHAR Jump[3]; 117 UCHAR OEMID[8]; 118 USHORT BytesPerSector; 119 UCHAR SectorsPerCluster; 120 UCHAR Unused0[7]; 121 UCHAR MediaId; 122 UCHAR Unused1[2]; 123 USHORT SectorsPerTrack; 124 USHORT Heads; 125 UCHAR Unused2[4]; 126 UCHAR Unused3[4]; 127 USHORT Unknown[2]; 128 ULONGLONG SectorCount; 129 ULONGLONG MftLocation; 130 ULONGLONG MftMirrLocation; 131 CHAR ClustersPerMftRecord; 132 UCHAR Unused4[3]; 133 CHAR ClustersPerIndexRecord; 134 UCHAR Unused5[3]; 135 ULONGLONG SerialNumber; 136 UCHAR Checksum[4]; 137 UCHAR BootStrap[426]; 138 USHORT EndSector; 139 UCHAR BootCodeAndData[7680]; // The remainder of the boot sector (8192 - 512) 140 } NTFS_BOOTSECTOR, *PNTFS_BOOTSECTOR; 141 C_ASSERT(sizeof(NTFS_BOOTSECTOR) == NTFS_BOOTSECTOR_SIZE); 142 143 // TODO: Add more bootsector structures! 144 145 #include <poppack.h> 146 147 148 /* LOCALS *******************************************************************/ 149 150 /** IFS_PROVIDER **/ 151 typedef struct _FILE_SYSTEM 152 { 153 PCWSTR FileSystemName; 154 PULIB_FORMAT FormatFunc; 155 PULIB_CHKDSK ChkdskFunc; 156 } FILE_SYSTEM, *PFILE_SYSTEM; 157 158 /* The list of file systems on which we can install ReactOS */ 159 static FILE_SYSTEM RegisteredFileSystems[] = 160 { 161 /* NOTE: The FAT formatter will automatically 162 * determine whether to use FAT12/16 or FAT32. */ 163 { L"FAT" , VfatFormat, VfatChkdsk }, 164 { L"FAT32", VfatFormat, VfatChkdsk }, 165 #if 0 166 { L"FATX" , VfatxFormat, VfatxChkdsk }, 167 { L"NTFS" , NtfsFormat, NtfsChkdsk }, 168 #endif 169 { L"BTRFS", BtrfsFormat, BtrfsChkdsk }, 170 #if 0 171 { L"EXT2" , Ext2Format, Ext2Chkdsk }, 172 { L"EXT3" , Ext2Format, Ext2Chkdsk }, 173 { L"EXT4" , Ext2Format, Ext2Chkdsk }, 174 #endif 175 }; 176 177 178 /* FUNCTIONS ****************************************************************/ 179 180 /** QueryAvailableFileSystemFormat() **/ 181 BOOLEAN 182 NTAPI 183 GetRegisteredFileSystems( 184 IN ULONG Index, 185 OUT PCWSTR* FileSystemName) 186 { 187 if (Index >= ARRAYSIZE(RegisteredFileSystems)) 188 return FALSE; 189 190 *FileSystemName = RegisteredFileSystems[Index].FileSystemName; 191 192 return TRUE; 193 } 194 195 196 /** GetProvider() **/ 197 static PFILE_SYSTEM 198 GetFileSystemByName( 199 IN PCWSTR FileSystemName) 200 { 201 #if 0 // Reenable when the list of registered FSes will again be dynamic 202 203 PLIST_ENTRY ListEntry; 204 PFILE_SYSTEM_ITEM Item; 205 206 ListEntry = List->ListHead.Flink; 207 while (ListEntry != &List->ListHead) 208 { 209 Item = CONTAINING_RECORD(ListEntry, FILE_SYSTEM_ITEM, ListEntry); 210 if (Item->FileSystemName && 211 (_wcsicmp(FileSystemName, Item->FileSystemName) == 0)) 212 { 213 return Item; 214 } 215 216 ListEntry = ListEntry->Flink; 217 } 218 219 #else 220 221 ULONG Count = ARRAYSIZE(RegisteredFileSystems); 222 PFILE_SYSTEM FileSystems = RegisteredFileSystems; 223 224 ASSERT(FileSystems && Count != 0); 225 226 while (Count--) 227 { 228 if (FileSystems->FileSystemName && 229 (_wcsicmp(FileSystemName, FileSystems->FileSystemName) == 0)) 230 { 231 return FileSystems; 232 } 233 234 ++FileSystems; 235 } 236 237 #endif 238 239 return NULL; 240 } 241 242 243 /** ChkdskEx() **/ 244 NTSTATUS 245 NTAPI 246 ChkdskFileSystem_UStr( 247 _In_ PUNICODE_STRING DriveRoot, 248 _In_ PCWSTR FileSystemName, 249 _In_ BOOLEAN FixErrors, 250 _In_ BOOLEAN Verbose, 251 _In_ BOOLEAN CheckOnlyIfDirty, 252 _In_ BOOLEAN ScanDrive, 253 _In_opt_ PFMIFSCALLBACK Callback) 254 { 255 PFILE_SYSTEM FileSystem; 256 NTSTATUS Status; 257 BOOLEAN Success; 258 259 FileSystem = GetFileSystemByName(FileSystemName); 260 261 if (!FileSystem || !FileSystem->ChkdskFunc) 262 { 263 // Success = FALSE; 264 // Callback(DONE, 0, &Success); 265 return STATUS_NOT_SUPPORTED; 266 } 267 268 Status = STATUS_SUCCESS; 269 Success = FileSystem->ChkdskFunc(DriveRoot, 270 Callback, 271 FixErrors, 272 Verbose, 273 CheckOnlyIfDirty, 274 ScanDrive, 275 NULL, 276 NULL, 277 NULL, 278 NULL, 279 (PULONG)&Status); 280 if (!Success) 281 DPRINT1("ChkdskFunc() failed with Status 0x%lx\n", Status); 282 283 // Callback(DONE, 0, &Success); 284 285 return Status; 286 } 287 288 NTSTATUS 289 NTAPI 290 ChkdskFileSystem( 291 _In_ PCWSTR DriveRoot, 292 _In_ PCWSTR FileSystemName, 293 _In_ BOOLEAN FixErrors, 294 _In_ BOOLEAN Verbose, 295 _In_ BOOLEAN CheckOnlyIfDirty, 296 _In_ BOOLEAN ScanDrive, 297 _In_opt_ PFMIFSCALLBACK Callback) 298 { 299 UNICODE_STRING DriveRootU; 300 301 RtlInitUnicodeString(&DriveRootU, DriveRoot); 302 return ChkdskFileSystem_UStr(&DriveRootU, 303 FileSystemName, 304 FixErrors, 305 Verbose, 306 CheckOnlyIfDirty, 307 ScanDrive, 308 Callback); 309 } 310 311 312 /** FormatEx() **/ 313 NTSTATUS 314 NTAPI 315 FormatFileSystem_UStr( 316 _In_ PUNICODE_STRING DriveRoot, 317 _In_ PCWSTR FileSystemName, 318 _In_ FMIFS_MEDIA_FLAG MediaFlag, 319 _In_opt_ PUNICODE_STRING Label, 320 _In_ BOOLEAN QuickFormat, 321 _In_ ULONG ClusterSize, 322 _In_opt_ PFMIFSCALLBACK Callback) 323 { 324 PFILE_SYSTEM FileSystem; 325 BOOLEAN Success; 326 BOOLEAN BackwardCompatible = FALSE; // Default to latest FS versions. 327 MEDIA_TYPE MediaType; 328 329 FileSystem = GetFileSystemByName(FileSystemName); 330 331 if (!FileSystem || !FileSystem->FormatFunc) 332 { 333 // Success = FALSE; 334 // Callback(DONE, 0, &Success); 335 return STATUS_NOT_SUPPORTED; 336 } 337 338 /* Set the BackwardCompatible flag in case we format with older FAT12/16 */ 339 if (_wcsicmp(FileSystemName, L"FAT") == 0) 340 BackwardCompatible = TRUE; 341 // else if (_wcsicmp(FileSystemName, L"FAT32") == 0) 342 // BackwardCompatible = FALSE; 343 344 /* Convert the FMIFS MediaFlag to a NT MediaType */ 345 // FIXME: Actually covert all the possible flags. 346 switch (MediaFlag) 347 { 348 case FMIFS_FLOPPY: 349 MediaType = F5_320_1024; // FIXME: This is hardfixed! 350 break; 351 case FMIFS_REMOVABLE: 352 MediaType = RemovableMedia; 353 break; 354 case FMIFS_HARDDISK: 355 MediaType = FixedMedia; 356 break; 357 default: 358 DPRINT1("Unknown FMIFS MediaFlag %d, converting 1-to-1 to NT MediaType\n", 359 MediaFlag); 360 MediaType = (MEDIA_TYPE)MediaFlag; 361 break; 362 } 363 364 Success = FileSystem->FormatFunc(DriveRoot, 365 Callback, 366 QuickFormat, 367 BackwardCompatible, 368 MediaType, 369 Label, 370 ClusterSize); 371 if (!Success) 372 DPRINT1("FormatFunc() failed\n"); 373 374 // Callback(DONE, 0, &Success); 375 376 return (Success ? STATUS_SUCCESS : STATUS_UNSUCCESSFUL); 377 } 378 379 NTSTATUS 380 NTAPI 381 FormatFileSystem( 382 _In_ PCWSTR DriveRoot, 383 _In_ PCWSTR FileSystemName, 384 _In_ FMIFS_MEDIA_FLAG MediaFlag, 385 _In_opt_ PCWSTR Label, 386 _In_ BOOLEAN QuickFormat, 387 _In_ ULONG ClusterSize, 388 _In_opt_ PFMIFSCALLBACK Callback) 389 { 390 UNICODE_STRING DriveRootU; 391 UNICODE_STRING LabelU; 392 393 RtlInitUnicodeString(&DriveRootU, DriveRoot); 394 RtlInitUnicodeString(&LabelU, Label); 395 396 return FormatFileSystem_UStr(&DriveRootU, 397 FileSystemName, 398 MediaFlag, 399 &LabelU, 400 QuickFormat, 401 ClusterSize, 402 Callback); 403 } 404 405 406 // 407 // Bootsector routines 408 // 409 410 NTSTATUS 411 InstallFatBootCode( 412 IN PCWSTR SrcPath, // FAT12/16 bootsector source file (on the installation medium) 413 IN HANDLE DstPath, // Where to save the bootsector built from the source + partition information 414 IN HANDLE RootPartition) // Partition holding the (old) FAT12/16 information 415 { 416 NTSTATUS Status; 417 UNICODE_STRING Name; 418 IO_STATUS_BLOCK IoStatusBlock; 419 LARGE_INTEGER FileOffset; 420 BOOTCODE OrigBootSector = {0}; 421 BOOTCODE NewBootSector = {0}; 422 423 /* Allocate and read the current original partition bootsector */ 424 Status = ReadBootCodeByHandle(&OrigBootSector, 425 RootPartition, 426 FAT_BOOTSECTOR_SIZE); 427 if (!NT_SUCCESS(Status)) 428 return Status; 429 430 /* Allocate and read the new bootsector from SrcPath */ 431 RtlInitUnicodeString(&Name, SrcPath); 432 Status = ReadBootCodeFromFile(&NewBootSector, 433 &Name, 434 FAT_BOOTSECTOR_SIZE); 435 if (!NT_SUCCESS(Status)) 436 { 437 FreeBootCode(&OrigBootSector); 438 return Status; 439 } 440 441 /* Adjust the bootsector (copy a part of the FAT12/16 BPB) */ 442 RtlCopyMemory(&((PFAT_BOOTSECTOR)NewBootSector.BootCode)->OemName, 443 &((PFAT_BOOTSECTOR)OrigBootSector.BootCode)->OemName, 444 FIELD_OFFSET(FAT_BOOTSECTOR, BootCodeAndData) - 445 FIELD_OFFSET(FAT_BOOTSECTOR, OemName)); 446 447 /* Free the original bootsector */ 448 FreeBootCode(&OrigBootSector); 449 450 /* Write the new bootsector to DstPath */ 451 FileOffset.QuadPart = 0ULL; 452 Status = NtWriteFile(DstPath, 453 NULL, 454 NULL, 455 NULL, 456 &IoStatusBlock, 457 NewBootSector.BootCode, 458 NewBootSector.Length, 459 &FileOffset, 460 NULL); 461 462 /* Free the new bootsector */ 463 FreeBootCode(&NewBootSector); 464 465 return Status; 466 } 467 468 NTSTATUS 469 InstallFat32BootCode( 470 IN PCWSTR SrcPath, // FAT32 bootsector source file (on the installation medium) 471 IN HANDLE DstPath, // Where to save the bootsector built from the source + partition information 472 IN HANDLE RootPartition) // Partition holding the (old) FAT32 information 473 { 474 NTSTATUS Status; 475 UNICODE_STRING Name; 476 IO_STATUS_BLOCK IoStatusBlock; 477 LARGE_INTEGER FileOffset; 478 USHORT BackupBootSector = 0; 479 BOOTCODE OrigBootSector = {0}; 480 BOOTCODE NewBootSector = {0}; 481 482 /* Allocate and read the current original partition bootsector */ 483 Status = ReadBootCodeByHandle(&OrigBootSector, 484 RootPartition, 485 FAT32_BOOTSECTOR_SIZE); 486 if (!NT_SUCCESS(Status)) 487 return Status; 488 489 /* Allocate and read the new bootsector (2 sectors) from SrcPath */ 490 RtlInitUnicodeString(&Name, SrcPath); 491 Status = ReadBootCodeFromFile(&NewBootSector, 492 &Name, 493 2 * FAT32_BOOTSECTOR_SIZE); 494 if (!NT_SUCCESS(Status)) 495 { 496 FreeBootCode(&OrigBootSector); 497 return Status; 498 } 499 500 /* Adjust the bootsector (copy a part of the FAT32 BPB) */ 501 RtlCopyMemory(&((PFAT32_BOOTSECTOR)NewBootSector.BootCode)->OemName, 502 &((PFAT32_BOOTSECTOR)OrigBootSector.BootCode)->OemName, 503 FIELD_OFFSET(FAT32_BOOTSECTOR, BootCodeAndData) - 504 FIELD_OFFSET(FAT32_BOOTSECTOR, OemName)); 505 506 /* 507 * We know we copy the boot code to a file only when DstPath != RootPartition, 508 * otherwise the boot code is copied to the specified root partition. 509 */ 510 if (DstPath != RootPartition) 511 { 512 /* Copy to a file: Disable the backup bootsector */ 513 ((PFAT32_BOOTSECTOR)NewBootSector.BootCode)->BackupBootSector = 0; 514 } 515 else 516 { 517 /* Copy to a disk: Get the location of the backup bootsector */ 518 BackupBootSector = ((PFAT32_BOOTSECTOR)OrigBootSector.BootCode)->BackupBootSector; 519 } 520 521 /* Free the original bootsector */ 522 FreeBootCode(&OrigBootSector); 523 524 /* Write the first sector of the new bootcode to DstPath sector 0 */ 525 FileOffset.QuadPart = 0ULL; 526 Status = NtWriteFile(DstPath, 527 NULL, 528 NULL, 529 NULL, 530 &IoStatusBlock, 531 NewBootSector.BootCode, 532 FAT32_BOOTSECTOR_SIZE, 533 &FileOffset, 534 NULL); 535 if (!NT_SUCCESS(Status)) 536 { 537 DPRINT1("NtWriteFile() failed (Status %lx)\n", Status); 538 FreeBootCode(&NewBootSector); 539 return Status; 540 } 541 542 if (DstPath == RootPartition) 543 { 544 /* Copy to a disk: Write the backup bootsector */ 545 if ((BackupBootSector != 0x0000) && (BackupBootSector != 0xFFFF)) 546 { 547 FileOffset.QuadPart = (ULONGLONG)((ULONG)BackupBootSector * FAT32_BOOTSECTOR_SIZE); 548 Status = NtWriteFile(DstPath, 549 NULL, 550 NULL, 551 NULL, 552 &IoStatusBlock, 553 NewBootSector.BootCode, 554 FAT32_BOOTSECTOR_SIZE, 555 &FileOffset, 556 NULL); 557 if (!NT_SUCCESS(Status)) 558 { 559 DPRINT1("NtWriteFile() failed (Status %lx)\n", Status); 560 FreeBootCode(&NewBootSector); 561 return Status; 562 } 563 } 564 } 565 566 /* Write the second sector of the new bootcode to boot disk sector 14 */ 567 // FileOffset.QuadPart = (ULONGLONG)(14 * FAT32_BOOTSECTOR_SIZE); 568 FileOffset.QuadPart = 14 * FAT32_BOOTSECTOR_SIZE; 569 Status = NtWriteFile(DstPath, // or really RootPartition ??? 570 NULL, 571 NULL, 572 NULL, 573 &IoStatusBlock, 574 ((PUCHAR)NewBootSector.BootCode + FAT32_BOOTSECTOR_SIZE), 575 FAT32_BOOTSECTOR_SIZE, 576 &FileOffset, 577 NULL); 578 if (!NT_SUCCESS(Status)) 579 { 580 DPRINT1("NtWriteFile() failed (Status %lx)\n", Status); 581 } 582 583 /* Free the new bootsector */ 584 FreeBootCode(&NewBootSector); 585 586 return Status; 587 } 588 589 NTSTATUS 590 InstallBtrfsBootCode( 591 IN PCWSTR SrcPath, // BTRFS bootsector source file (on the installation medium) 592 IN HANDLE DstPath, // Where to save the bootsector built from the source + partition information 593 IN HANDLE RootPartition) // Partition holding the (old) BTRFS information 594 { 595 NTSTATUS Status; 596 NTSTATUS LockStatus; 597 UNICODE_STRING Name; 598 IO_STATUS_BLOCK IoStatusBlock; 599 LARGE_INTEGER FileOffset; 600 PARTITION_INFORMATION_EX PartInfo; 601 BOOTCODE NewBootSector = {0}; 602 603 /* Allocate and read the new bootsector from SrcPath */ 604 RtlInitUnicodeString(&Name, SrcPath); 605 Status = ReadBootCodeFromFile(&NewBootSector, 606 &Name, 607 BTRFS_BOOTSECTOR_SIZE); 608 if (!NT_SUCCESS(Status)) 609 return Status; 610 611 /* 612 * The BTRFS driver requires the volume to be locked in order to modify 613 * the first sectors of the partition, even though they are outside the 614 * file-system space / in the reserved area (they are situated before 615 * the super-block at 0x1000) and is in principle allowed by the NT 616 * storage stack. 617 * So we lock here in order to write the bootsector at sector 0. 618 * If locking fails, we ignore and continue nonetheless. 619 */ 620 LockStatus = NtFsControlFile(DstPath, 621 NULL, 622 NULL, 623 NULL, 624 &IoStatusBlock, 625 FSCTL_LOCK_VOLUME, 626 NULL, 627 0, 628 NULL, 629 0); 630 if (!NT_SUCCESS(LockStatus)) 631 { 632 DPRINT1("WARNING: Failed to lock BTRFS volume for writing bootsector! Operations may fail! (Status 0x%lx)\n", LockStatus); 633 } 634 635 /* Obtain partition info and write it to the bootsector */ 636 Status = NtDeviceIoControlFile(RootPartition, 637 NULL, 638 NULL, 639 NULL, 640 &IoStatusBlock, 641 IOCTL_DISK_GET_PARTITION_INFO_EX, 642 NULL, 643 0, 644 &PartInfo, 645 sizeof(PartInfo)); 646 if (!NT_SUCCESS(Status)) 647 { 648 DPRINT1("IOCTL_DISK_GET_PARTITION_INFO_EX failed (Status %lx)\n", Status); 649 goto Quit; 650 } 651 652 /* Write new bootsector to RootPath */ 653 ((PBTRFS_BOOTSECTOR)NewBootSector.BootCode)->PartitionStartLBA = 654 PartInfo.StartingOffset.QuadPart / SECTORSIZE; 655 656 /* Write sector 0 */ 657 FileOffset.QuadPart = 0ULL; 658 Status = NtWriteFile(DstPath, 659 NULL, 660 NULL, 661 NULL, 662 &IoStatusBlock, 663 NewBootSector.BootCode, 664 NewBootSector.Length, 665 &FileOffset, 666 NULL); 667 if (!NT_SUCCESS(Status)) 668 { 669 DPRINT1("NtWriteFile() failed (Status %lx)\n", Status); 670 goto Quit; 671 } 672 673 Quit: 674 /* Unlock the volume */ 675 LockStatus = NtFsControlFile(DstPath, 676 NULL, 677 NULL, 678 NULL, 679 &IoStatusBlock, 680 FSCTL_UNLOCK_VOLUME, 681 NULL, 682 0, 683 NULL, 684 0); 685 if (!NT_SUCCESS(LockStatus)) 686 { 687 DPRINT1("Failed to unlock BTRFS volume (Status 0x%lx)\n", LockStatus); 688 } 689 690 /* Free the new bootsector */ 691 FreeBootCode(&NewBootSector); 692 693 return Status; 694 } 695 696 NTSTATUS 697 InstallNtfsBootCode( 698 IN PCWSTR SrcPath, // NTFS bootsector source file (on the installation medium) 699 IN HANDLE DstPath, // Where to save the bootsector built from the source + partition information 700 IN HANDLE RootPartition) // Partition holding the (old) NTFS information 701 { 702 NTSTATUS Status; 703 UNICODE_STRING Name; 704 IO_STATUS_BLOCK IoStatusBlock; 705 LARGE_INTEGER FileOffset; 706 BOOTCODE OrigBootSector = {0}; 707 BOOTCODE NewBootSector = {0}; 708 709 /* Allocate and read the current original partition bootsector */ 710 Status = ReadBootCodeByHandle(&OrigBootSector, RootPartition, NTFS_BOOTSECTOR_SIZE); 711 if (!NT_SUCCESS(Status)) 712 { 713 DPRINT1("InstallNtfsBootCode: Status %lx\n", Status); 714 return Status; 715 } 716 717 /* Allocate and read the new bootsector (16 sectors) from SrcPath */ 718 RtlInitUnicodeString(&Name, SrcPath); 719 Status = ReadBootCodeFromFile(&NewBootSector, &Name, NTFS_BOOTSECTOR_SIZE); 720 if (!NT_SUCCESS(Status)) 721 { 722 DPRINT1("InstallNtfsBootCode: Status %lx\n", Status); 723 FreeBootCode(&OrigBootSector); 724 return Status; 725 } 726 727 /* Adjust the bootsector (copy a part of the NTFS BPB) */ 728 RtlCopyMemory(&((PNTFS_BOOTSECTOR)NewBootSector.BootCode)->OEMID, 729 &((PNTFS_BOOTSECTOR)OrigBootSector.BootCode)->OEMID, 730 FIELD_OFFSET(NTFS_BOOTSECTOR, BootStrap) - FIELD_OFFSET(NTFS_BOOTSECTOR, OEMID)); 731 732 /* Write sector 0 */ 733 FileOffset.QuadPart = 0ULL; 734 Status = NtWriteFile(DstPath, 735 NULL, 736 NULL, 737 NULL, 738 &IoStatusBlock, 739 NewBootSector.BootCode, 740 NewBootSector.Length, 741 &FileOffset, 742 NULL); 743 if (!NT_SUCCESS(Status)) 744 { 745 DPRINT1("NtWriteFile() failed (Status %lx)\n", Status); 746 goto Quit; 747 } 748 749 Quit: 750 /* Free the new bootsector */ 751 FreeBootCode(&NewBootSector); 752 753 return Status; 754 } 755 756 757 // 758 // Formatting routines 759 // 760 761 NTSTATUS 762 NTAPI 763 ChkdskVolume( 764 _In_ PVOLINFO Volume, 765 _In_ BOOLEAN FixErrors, 766 _In_ BOOLEAN Verbose, 767 _In_ BOOLEAN CheckOnlyIfDirty, 768 _In_ BOOLEAN ScanDrive, 769 _In_opt_ PFMIFSCALLBACK Callback) 770 { 771 /* Do not check a volume with an unknown file system */ 772 if (!*Volume->FileSystem) 773 return STATUS_UNRECOGNIZED_VOLUME; 774 775 /* Check the volume */ 776 DPRINT("Volume->DeviceName: %S\n", Volume->DeviceName); 777 return ChkdskFileSystem(Volume->DeviceName, 778 Volume->FileSystem, 779 FixErrors, 780 Verbose, 781 CheckOnlyIfDirty, 782 ScanDrive, 783 Callback); 784 } 785 786 NTSTATUS 787 NTAPI 788 ChkdskPartition( 789 _In_ PPARTENTRY PartEntry, 790 _In_ BOOLEAN FixErrors, 791 _In_ BOOLEAN Verbose, 792 _In_ BOOLEAN CheckOnlyIfDirty, 793 _In_ BOOLEAN ScanDrive, 794 _In_opt_ PFMIFSCALLBACK Callback) 795 { 796 ASSERT(PartEntry->IsPartitioned && PartEntry->PartitionNumber != 0); 797 ASSERT(PartEntry->Volume); 798 799 // if (!PartEntry->Volume) { check_raw_sectors(); } else { check_FS(); } 800 801 /* Check the associated volume */ 802 return ChkdskVolume(&PartEntry->Volume->Info, 803 FixErrors, 804 Verbose, 805 CheckOnlyIfDirty, 806 ScanDrive, 807 Callback); 808 } 809 810 NTSTATUS 811 NTAPI 812 FormatVolume( 813 _In_ PVOLINFO Volume, 814 _In_ PCWSTR FileSystemName, 815 _In_ FMIFS_MEDIA_FLAG MediaFlag, 816 _In_opt_ PCWSTR Label, 817 _In_ BOOLEAN QuickFormat, 818 _In_ ULONG ClusterSize, 819 _In_opt_ PFMIFSCALLBACK Callback) 820 { 821 NTSTATUS Status; 822 823 if (!FileSystemName || !*FileSystemName) 824 { 825 DPRINT1("No file system specified\n"); 826 return STATUS_UNRECOGNIZED_VOLUME; 827 } 828 829 /* Format the volume */ 830 DPRINT("Volume->DeviceName: %S\n", Volume->DeviceName); 831 Status = FormatFileSystem(Volume->DeviceName, 832 FileSystemName, 833 MediaFlag, 834 Label, 835 QuickFormat, 836 ClusterSize, 837 Callback); 838 if (!NT_SUCCESS(Status)) 839 return Status; 840 841 /* Set the new volume's file system and label */ 842 RtlStringCbCopyW(Volume->FileSystem, sizeof(Volume->FileSystem), FileSystemName); 843 if (!Label) Label = L""; 844 RtlStringCbCopyW(Volume->VolumeLabel, sizeof(Volume->VolumeLabel), Label); 845 846 return STATUS_SUCCESS; 847 } 848 849 NTSTATUS 850 NTAPI 851 FormatPartition( 852 _In_ PPARTENTRY PartEntry, 853 _In_ PCWSTR FileSystemName, 854 _In_ FMIFS_MEDIA_FLAG MediaFlag, 855 _In_opt_ PCWSTR Label, 856 _In_ BOOLEAN QuickFormat, 857 _In_ ULONG ClusterSize, 858 _In_opt_ PFMIFSCALLBACK Callback) 859 { 860 NTSTATUS Status; 861 PDISKENTRY DiskEntry = PartEntry->DiskEntry; 862 UCHAR PartitionType; 863 864 ASSERT(PartEntry->IsPartitioned && PartEntry->PartitionNumber != 0); 865 866 if (!FileSystemName || !*FileSystemName) 867 { 868 DPRINT1("No file system specified\n"); 869 return STATUS_UNRECOGNIZED_VOLUME; 870 } 871 872 /* 873 * Prepare the partition for formatting (for MBR disks, reset the 874 * partition type), and adjust the file system name in case of FAT 875 * vs. FAT32, depending on the geometry of the partition. 876 */ 877 878 // FIXME: Do this only if QuickFormat == FALSE? What about FAT handling? 879 880 /* 881 * Retrieve a partition type as a hint only. It will be used to determine 882 * whether to actually use FAT12/16 or FAT32 file system, depending on the 883 * geometry of the partition. If the partition resides on an MBR disk, 884 * the partition style will be reset to this value as well, unless the 885 * partition is OEM. 886 */ 887 PartitionType = FileSystemToMBRPartitionType(FileSystemName, 888 PartEntry->StartSector.QuadPart, 889 PartEntry->SectorCount.QuadPart); 890 if (PartitionType == PARTITION_ENTRY_UNUSED) 891 { 892 /* Unknown file system */ 893 DPRINT1("Unknown file system '%S'\n", FileSystemName); 894 return STATUS_UNRECOGNIZED_VOLUME; 895 } 896 897 /* Reset the MBR partition type, unless this is an OEM partition */ 898 if (DiskEntry->DiskStyle == PARTITION_STYLE_MBR) 899 { 900 if (!IsOEMPartition(PartEntry->PartitionType)) 901 SetMBRPartitionType(PartEntry, PartitionType); 902 } 903 904 /* 905 * Adjust the file system name in case of FAT vs. FAT32, according to 906 * the type of partition returned by FileSystemToMBRPartitionType(). 907 */ 908 if (_wcsicmp(FileSystemName, L"FAT") == 0) 909 { 910 if ((PartitionType == PARTITION_FAT32) || 911 (PartitionType == PARTITION_FAT32_XINT13)) 912 { 913 FileSystemName = L"FAT32"; 914 } 915 } 916 917 /* Commit the partition changes to the disk */ 918 Status = WritePartitions(DiskEntry); 919 if (!NT_SUCCESS(Status)) 920 { 921 DPRINT1("WritePartitions(disk %lu) failed, Status 0x%08lx\n", 922 DiskEntry->DiskNumber, Status); 923 return STATUS_PARTITION_FAILURE; 924 } 925 926 /* We must have an associated volume now */ 927 ASSERT(PartEntry->Volume); 928 929 /* Format the associated volume */ 930 Status = FormatVolume(&PartEntry->Volume->Info, 931 FileSystemName, 932 MediaFlag, 933 Label, 934 QuickFormat, 935 ClusterSize, 936 Callback); 937 if (!NT_SUCCESS(Status)) 938 return Status; 939 940 PartEntry->Volume->FormatState = Formatted; 941 PartEntry->Volume->New = FALSE; 942 return STATUS_SUCCESS; 943 } 944 945 946 // 947 // FileSystem Volume Operations Queue 948 // 949 950 static FSVOL_OP 951 DoFormatting( 952 _In_ PVOLENTRY Volume, 953 _In_opt_ PVOID Context, 954 _In_opt_ PFSVOL_CALLBACK FsVolCallback) 955 { 956 FSVOL_OP Result; 957 NTSTATUS Status = STATUS_SUCCESS; 958 PPARTENTRY PartEntry; 959 FORMAT_VOLUME_INFO FmtInfo = {0}; 960 961 PartEntry = Volume->PartEntry; 962 ASSERT(PartEntry && (PartEntry->Volume == Volume)); 963 964 FmtInfo.Volume = Volume; 965 966 RetryFormat: 967 Result = FsVolCallback(Context, 968 FSVOLNOTIFY_STARTFORMAT, 969 (ULONG_PTR)&FmtInfo, 970 FSVOL_FORMAT); 971 if (Result != FSVOL_DOIT) 972 goto EndFormat; 973 974 ASSERT(FmtInfo.FileSystemName && *FmtInfo.FileSystemName); 975 976 /* Format the partition */ 977 Status = FormatPartition(PartEntry, 978 FmtInfo.FileSystemName, 979 FmtInfo.MediaFlag, 980 FmtInfo.Label, 981 FmtInfo.QuickFormat, 982 FmtInfo.ClusterSize, 983 FmtInfo.Callback); 984 if (!NT_SUCCESS(Status)) 985 { 986 // FmtInfo.NtPathPartition = PathBuffer; 987 FmtInfo.ErrorStatus = Status; 988 989 Result = FsVolCallback(Context, 990 FSVOLNOTIFY_FORMATERROR, 991 (ULONG_PTR)&FmtInfo, 992 0); 993 if (Result == FSVOL_RETRY) 994 goto RetryFormat; 995 // else if (Result == FSVOL_ABORT || Result == FSVOL_SKIP), stop. 996 } 997 998 EndFormat: 999 /* This notification is always sent, even in case of error or abort */ 1000 FmtInfo.ErrorStatus = Status; 1001 FsVolCallback(Context, 1002 FSVOLNOTIFY_ENDFORMAT, 1003 (ULONG_PTR)&FmtInfo, 1004 0); 1005 return Result; 1006 } 1007 1008 static FSVOL_OP 1009 DoChecking( 1010 _In_ PVOLENTRY Volume, 1011 _In_opt_ PVOID Context, 1012 _In_opt_ PFSVOL_CALLBACK FsVolCallback) 1013 { 1014 FSVOL_OP Result; 1015 NTSTATUS Status = STATUS_SUCCESS; 1016 CHECK_VOLUME_INFO ChkInfo = {0}; 1017 1018 ASSERT(*Volume->Info.FileSystem); 1019 1020 ChkInfo.Volume = Volume; 1021 1022 RetryCheck: 1023 Result = FsVolCallback(Context, 1024 FSVOLNOTIFY_STARTCHECK, 1025 (ULONG_PTR)&ChkInfo, 1026 FSVOL_CHECK); 1027 if (Result != FSVOL_DOIT) 1028 goto EndCheck; 1029 1030 /* Check the volume */ 1031 Status = ChkdskVolume(&Volume->Info, 1032 ChkInfo.FixErrors, 1033 ChkInfo.Verbose, 1034 ChkInfo.CheckOnlyIfDirty, 1035 ChkInfo.ScanDrive, 1036 ChkInfo.Callback); 1037 1038 /* If volume checking succeeded, or if it is not supported 1039 * with the current file system, disable checks on the volume */ 1040 if (NT_SUCCESS(Status) || (Status == STATUS_NOT_SUPPORTED)) 1041 Volume->NeedsCheck = FALSE; 1042 1043 if (!NT_SUCCESS(Status)) 1044 { 1045 // ChkInfo.NtPathPartition = PathBuffer; 1046 ChkInfo.ErrorStatus = Status; 1047 1048 Result = FsVolCallback(Context, 1049 FSVOLNOTIFY_CHECKERROR, 1050 (ULONG_PTR)&ChkInfo, 1051 0); 1052 if (Result == FSVOL_RETRY) 1053 goto RetryCheck; 1054 // else if (Result == FSVOL_ABORT || Result == FSVOL_SKIP), stop. 1055 1056 // Volume->NeedsCheck = FALSE; 1057 } 1058 1059 EndCheck: 1060 /* This notification is always sent, even in case of error or abort */ 1061 ChkInfo.ErrorStatus = Status; 1062 FsVolCallback(Context, 1063 FSVOLNOTIFY_ENDCHECK, 1064 (ULONG_PTR)&ChkInfo, 1065 0); 1066 return Result; 1067 } 1068 1069 static 1070 PVOLENTRY 1071 GetNextUnformattedVolume( 1072 _In_ PPARTLIST List, 1073 _In_opt_ PVOLENTRY Volume) 1074 { 1075 PLIST_ENTRY Entry; 1076 1077 for (;;) 1078 { 1079 /* If we have a current volume, get the next one, otherwise get the first */ 1080 Entry = (Volume ? &Volume->ListEntry : &List->VolumesList); 1081 Entry = Entry->Flink; 1082 1083 if (Entry == &List->VolumesList) 1084 return NULL; 1085 1086 Volume = CONTAINING_RECORD(Entry, VOLENTRY, ListEntry); 1087 if (Volume->New && (Volume->FormatState == Unformatted)) 1088 { 1089 /* Found a candidate, return it */ 1090 return Volume; 1091 } 1092 } 1093 } 1094 1095 BOOLEAN 1096 NTAPI 1097 FsVolCommitOpsQueue( 1098 _In_ PPARTLIST PartitionList, 1099 _In_ PVOLENTRY SystemVolume, 1100 _In_ PVOLENTRY InstallVolume, 1101 _In_opt_ PFSVOL_CALLBACK FsVolCallback, 1102 _In_opt_ PVOID Context) 1103 { 1104 BOOLEAN Success = TRUE; // Suppose success originally. 1105 FSVOL_OP Result; 1106 PLIST_ENTRY Entry; 1107 PVOLENTRY Volume; 1108 1109 /* Machine state for the format step */ 1110 typedef enum _FORMATMACHINESTATE 1111 { 1112 Start, 1113 FormatSystemVolume, 1114 FormatInstallVolume, 1115 FormatOtherVolume, 1116 FormatDone 1117 } FORMATMACHINESTATE; 1118 FORMATMACHINESTATE FormatState, OldFormatState; 1119 static const PCSTR FormatStateNames[] = { 1120 "Start", 1121 "FormatSystemVolume", 1122 "FormatInstallVolume", 1123 "FormatOtherVolume", 1124 "FormatDone" 1125 }; 1126 1127 ASSERT(PartitionList && SystemVolume && InstallVolume); 1128 1129 /* Commit all partition changes to all the disks */ 1130 if (!WritePartitionsToDisk(PartitionList)) 1131 { 1132 DPRINT("WritePartitionsToDisk() failed\n"); 1133 /* Result = */ FsVolCallback(Context, 1134 FSVOLNOTIFY_PARTITIONERROR, 1135 STATUS_PARTITION_FAILURE, // FIXME 1136 0); 1137 return FALSE; 1138 } 1139 1140 // 1141 // FIXME: Should we do the following here, or in the caller? 1142 // 1143 /* 1144 * In all cases, whether or not we are going to perform a formatting, 1145 * we must perform a file system check of both the system and the 1146 * installation volumes. 1147 */ 1148 SystemVolume->NeedsCheck = TRUE; 1149 InstallVolume->NeedsCheck = TRUE; 1150 1151 Result = FsVolCallback(Context, 1152 FSVOLNOTIFY_STARTQUEUE, 1153 0, 0); 1154 if (Result == FSVOL_ABORT) 1155 return FALSE; 1156 1157 /* 1158 * Commit the Format queue 1159 */ 1160 1161 Result = FsVolCallback(Context, 1162 FSVOLNOTIFY_STARTSUBQUEUE, 1163 FSVOL_FORMAT, 1164 0); 1165 if (Result == FSVOL_ABORT) 1166 return FALSE; 1167 /** HACK!! **/ 1168 if (Result == FSVOL_SKIP) 1169 goto StartCheckQueue; 1170 /** END HACK!! **/ 1171 1172 /* Reset the formatter machine state */ 1173 FormatState = Start; 1174 Volume = NULL; 1175 NextFormat: 1176 OldFormatState = FormatState; 1177 switch (FormatState) 1178 { 1179 case Start: 1180 { 1181 /* 1182 * We start by formatting the system volume in case it is new 1183 * (it didn't exist before) and is not the same as the installation 1184 * volume. Otherwise we just require a file system check on it, 1185 * and start by formatting the installation volume instead. 1186 */ 1187 if (SystemVolume != InstallVolume) 1188 { 1189 Volume = SystemVolume; 1190 1191 if (Volume->FormatState == Unformatted) 1192 { 1193 // TODO: Should we let the user use a custom file system, 1194 // or should we always use FAT(32) for it? 1195 // For "compatibility", FAT(32) would be best indeed. 1196 1197 FormatState = FormatSystemVolume; 1198 DPRINT1("FormatState: %s --> %s\n", 1199 FormatStateNames[OldFormatState], FormatStateNames[FormatState]); 1200 break; 1201 } 1202 1203 /* The system volume is separate, so it had better be formatted! */ 1204 ASSERT(Volume->FormatState == Formatted); 1205 1206 /* Require a file system check on the system volume too */ 1207 Volume->NeedsCheck = TRUE; 1208 } 1209 __fallthrough; 1210 } 1211 1212 case FormatSystemVolume: 1213 { 1214 Volume = InstallVolume; 1215 1216 FormatState = FormatInstallVolume; 1217 DPRINT1("FormatState: %s --> %s\n", 1218 FormatStateNames[OldFormatState], FormatStateNames[FormatState]); 1219 break; 1220 } 1221 1222 case FormatInstallVolume: 1223 /* Restart volume enumeration */ 1224 Volume = NULL; 1225 case FormatOtherVolume: 1226 { 1227 Volume = GetNextUnformattedVolume(PartitionList, Volume); 1228 1229 FormatState = (Volume ? FormatOtherVolume : FormatDone); 1230 DPRINT1("FormatState: %s --> %s\n", 1231 FormatStateNames[OldFormatState], FormatStateNames[FormatState]); 1232 if (Volume) 1233 break; 1234 __fallthrough; 1235 } 1236 1237 case FormatDone: 1238 { 1239 DPRINT1("FormatState: FormatDone\n"); 1240 Success = TRUE; 1241 goto EndFormat; 1242 } 1243 DEFAULT_UNREACHABLE; 1244 } 1245 1246 Result = DoFormatting(Volume, Context, FsVolCallback); 1247 if (Result == FSVOL_ABORT) 1248 { 1249 Success = FALSE; 1250 goto Quit; 1251 } 1252 /* Schedule a check for this volume */ 1253 Volume->NeedsCheck = TRUE; 1254 /* Go to the next volume to be formatted */ 1255 goto NextFormat; 1256 1257 EndFormat: 1258 FsVolCallback(Context, 1259 FSVOLNOTIFY_ENDSUBQUEUE, 1260 FSVOL_FORMAT, 1261 0); 1262 1263 1264 /* 1265 * Commit the CheckFS queue 1266 */ 1267 1268 StartCheckQueue: 1269 Result = FsVolCallback(Context, 1270 FSVOLNOTIFY_STARTSUBQUEUE, 1271 FSVOL_CHECK, 1272 0); 1273 if (Result == FSVOL_ABORT) 1274 return FALSE; 1275 1276 /* Loop through each unchecked volume and do the check */ 1277 for (Entry = PartitionList->VolumesList.Flink; 1278 Entry != &PartitionList->VolumesList; 1279 Entry = Entry->Flink) 1280 { 1281 Volume = CONTAINING_RECORD(Entry, VOLENTRY, ListEntry); 1282 if (!Volume->NeedsCheck) 1283 continue; 1284 1285 /* Found a candidate */ 1286 ASSERT(Volume->FormatState == Formatted); 1287 Result = DoChecking(Volume, Context, FsVolCallback); 1288 if (Result == FSVOL_ABORT) 1289 { 1290 Success = FALSE; 1291 goto Quit; 1292 } 1293 /* Go to the next volume to be checked */ 1294 } 1295 Success = TRUE; 1296 1297 FsVolCallback(Context, 1298 FSVOLNOTIFY_ENDSUBQUEUE, 1299 FSVOL_CHECK, 1300 0); 1301 1302 1303 Quit: 1304 /* All the queues have been committed */ 1305 FsVolCallback(Context, 1306 FSVOLNOTIFY_ENDQUEUE, 1307 Success, 1308 0); 1309 return Success; 1310 } 1311 1312 /* EOF */ 1313