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 GetRegisteredFileSystems( 183 IN ULONG Index, 184 OUT PCWSTR* FileSystemName) 185 { 186 if (Index >= ARRAYSIZE(RegisteredFileSystems)) 187 return FALSE; 188 189 *FileSystemName = RegisteredFileSystems[Index].FileSystemName; 190 191 return TRUE; 192 } 193 194 195 /** GetProvider() **/ 196 static PFILE_SYSTEM 197 GetFileSystemByName( 198 IN PCWSTR FileSystemName) 199 { 200 #if 0 // Reenable when the list of registered FSes will again be dynamic 201 202 PLIST_ENTRY ListEntry; 203 PFILE_SYSTEM_ITEM Item; 204 205 ListEntry = List->ListHead.Flink; 206 while (ListEntry != &List->ListHead) 207 { 208 Item = CONTAINING_RECORD(ListEntry, FILE_SYSTEM_ITEM, ListEntry); 209 if (Item->FileSystemName && 210 (_wcsicmp(FileSystemName, Item->FileSystemName) == 0)) 211 { 212 return Item; 213 } 214 215 ListEntry = ListEntry->Flink; 216 } 217 218 #else 219 220 ULONG Count = ARRAYSIZE(RegisteredFileSystems); 221 PFILE_SYSTEM FileSystems = RegisteredFileSystems; 222 223 ASSERT(FileSystems && Count != 0); 224 225 while (Count--) 226 { 227 if (FileSystems->FileSystemName && 228 (_wcsicmp(FileSystemName, FileSystems->FileSystemName) == 0)) 229 { 230 return FileSystems; 231 } 232 233 ++FileSystems; 234 } 235 236 #endif 237 238 return NULL; 239 } 240 241 242 /** ChkdskEx() **/ 243 NTSTATUS 244 ChkdskFileSystem_UStr( 245 _In_ PUNICODE_STRING DriveRoot, 246 _In_ PCWSTR FileSystemName, 247 _In_ BOOLEAN FixErrors, 248 _In_ BOOLEAN Verbose, 249 _In_ BOOLEAN CheckOnlyIfDirty, 250 _In_ BOOLEAN ScanDrive, 251 _In_opt_ PFMIFSCALLBACK Callback) 252 { 253 PFILE_SYSTEM FileSystem; 254 NTSTATUS Status; 255 BOOLEAN Success; 256 257 FileSystem = GetFileSystemByName(FileSystemName); 258 259 if (!FileSystem || !FileSystem->ChkdskFunc) 260 { 261 // Success = FALSE; 262 // Callback(DONE, 0, &Success); 263 return STATUS_NOT_SUPPORTED; 264 } 265 266 Status = STATUS_SUCCESS; 267 Success = FileSystem->ChkdskFunc(DriveRoot, 268 Callback, 269 FixErrors, 270 Verbose, 271 CheckOnlyIfDirty, 272 ScanDrive, 273 NULL, 274 NULL, 275 NULL, 276 NULL, 277 (PULONG)&Status); 278 if (!Success) 279 DPRINT1("ChkdskFunc() failed with Status 0x%lx\n", Status); 280 281 // Callback(DONE, 0, &Success); 282 283 return Status; 284 } 285 286 NTSTATUS 287 ChkdskFileSystem( 288 _In_ PCWSTR DriveRoot, 289 _In_ PCWSTR FileSystemName, 290 _In_ BOOLEAN FixErrors, 291 _In_ BOOLEAN Verbose, 292 _In_ BOOLEAN CheckOnlyIfDirty, 293 _In_ BOOLEAN ScanDrive, 294 _In_opt_ PFMIFSCALLBACK Callback) 295 { 296 UNICODE_STRING DriveRootU; 297 298 RtlInitUnicodeString(&DriveRootU, DriveRoot); 299 return ChkdskFileSystem_UStr(&DriveRootU, 300 FileSystemName, 301 FixErrors, 302 Verbose, 303 CheckOnlyIfDirty, 304 ScanDrive, 305 Callback); 306 } 307 308 309 /** FormatEx() **/ 310 NTSTATUS 311 FormatFileSystem_UStr( 312 _In_ PUNICODE_STRING DriveRoot, 313 _In_ PCWSTR FileSystemName, 314 _In_ FMIFS_MEDIA_FLAG MediaFlag, 315 _In_opt_ PUNICODE_STRING Label, 316 _In_ BOOLEAN QuickFormat, 317 _In_ ULONG ClusterSize, 318 _In_opt_ PFMIFSCALLBACK Callback) 319 { 320 PFILE_SYSTEM FileSystem; 321 BOOLEAN Success; 322 BOOLEAN BackwardCompatible = FALSE; // Default to latest FS versions. 323 MEDIA_TYPE MediaType; 324 325 FileSystem = GetFileSystemByName(FileSystemName); 326 327 if (!FileSystem || !FileSystem->FormatFunc) 328 { 329 // Success = FALSE; 330 // Callback(DONE, 0, &Success); 331 return STATUS_NOT_SUPPORTED; 332 } 333 334 /* Set the BackwardCompatible flag in case we format with older FAT12/16 */ 335 if (_wcsicmp(FileSystemName, L"FAT") == 0) 336 BackwardCompatible = TRUE; 337 // else if (_wcsicmp(FileSystemName, L"FAT32") == 0) 338 // BackwardCompatible = FALSE; 339 340 /* Convert the FMIFS MediaFlag to a NT MediaType */ 341 // FIXME: Actually covert all the possible flags. 342 switch (MediaFlag) 343 { 344 case FMIFS_FLOPPY: 345 MediaType = F5_320_1024; // FIXME: This is hardfixed! 346 break; 347 case FMIFS_REMOVABLE: 348 MediaType = RemovableMedia; 349 break; 350 case FMIFS_HARDDISK: 351 MediaType = FixedMedia; 352 break; 353 default: 354 DPRINT1("Unknown FMIFS MediaFlag %d, converting 1-to-1 to NT MediaType\n", 355 MediaFlag); 356 MediaType = (MEDIA_TYPE)MediaFlag; 357 break; 358 } 359 360 Success = FileSystem->FormatFunc(DriveRoot, 361 Callback, 362 QuickFormat, 363 BackwardCompatible, 364 MediaType, 365 Label, 366 ClusterSize); 367 if (!Success) 368 DPRINT1("FormatFunc() failed\n"); 369 370 // Callback(DONE, 0, &Success); 371 372 return (Success ? STATUS_SUCCESS : STATUS_UNSUCCESSFUL); 373 } 374 375 NTSTATUS 376 FormatFileSystem( 377 _In_ PCWSTR DriveRoot, 378 _In_ PCWSTR FileSystemName, 379 _In_ FMIFS_MEDIA_FLAG MediaFlag, 380 _In_opt_ PCWSTR Label, 381 _In_ BOOLEAN QuickFormat, 382 _In_ ULONG ClusterSize, 383 _In_opt_ PFMIFSCALLBACK Callback) 384 { 385 UNICODE_STRING DriveRootU; 386 UNICODE_STRING LabelU; 387 388 RtlInitUnicodeString(&DriveRootU, DriveRoot); 389 RtlInitUnicodeString(&LabelU, Label); 390 391 return FormatFileSystem_UStr(&DriveRootU, 392 FileSystemName, 393 MediaFlag, 394 &LabelU, 395 QuickFormat, 396 ClusterSize, 397 Callback); 398 } 399 400 401 // 402 // Bootsector routines 403 // 404 405 NTSTATUS 406 InstallFatBootCode( 407 IN PCWSTR SrcPath, // FAT12/16 bootsector source file (on the installation medium) 408 IN HANDLE DstPath, // Where to save the bootsector built from the source + partition information 409 IN HANDLE RootPartition) // Partition holding the (old) FAT12/16 information 410 { 411 NTSTATUS Status; 412 UNICODE_STRING Name; 413 IO_STATUS_BLOCK IoStatusBlock; 414 LARGE_INTEGER FileOffset; 415 BOOTCODE OrigBootSector = {0}; 416 BOOTCODE NewBootSector = {0}; 417 418 /* Allocate and read the current original partition bootsector */ 419 Status = ReadBootCodeByHandle(&OrigBootSector, 420 RootPartition, 421 FAT_BOOTSECTOR_SIZE); 422 if (!NT_SUCCESS(Status)) 423 return Status; 424 425 /* Allocate and read the new bootsector from SrcPath */ 426 RtlInitUnicodeString(&Name, SrcPath); 427 Status = ReadBootCodeFromFile(&NewBootSector, 428 &Name, 429 FAT_BOOTSECTOR_SIZE); 430 if (!NT_SUCCESS(Status)) 431 { 432 FreeBootCode(&OrigBootSector); 433 return Status; 434 } 435 436 /* Adjust the bootsector (copy a part of the FAT12/16 BPB) */ 437 RtlCopyMemory(&((PFAT_BOOTSECTOR)NewBootSector.BootCode)->OemName, 438 &((PFAT_BOOTSECTOR)OrigBootSector.BootCode)->OemName, 439 FIELD_OFFSET(FAT_BOOTSECTOR, BootCodeAndData) - 440 FIELD_OFFSET(FAT_BOOTSECTOR, OemName)); 441 442 /* Free the original bootsector */ 443 FreeBootCode(&OrigBootSector); 444 445 /* Write the new bootsector to DstPath */ 446 FileOffset.QuadPart = 0ULL; 447 Status = NtWriteFile(DstPath, 448 NULL, 449 NULL, 450 NULL, 451 &IoStatusBlock, 452 NewBootSector.BootCode, 453 NewBootSector.Length, 454 &FileOffset, 455 NULL); 456 457 /* Free the new bootsector */ 458 FreeBootCode(&NewBootSector); 459 460 return Status; 461 } 462 463 NTSTATUS 464 InstallFat32BootCode( 465 IN PCWSTR SrcPath, // FAT32 bootsector source file (on the installation medium) 466 IN HANDLE DstPath, // Where to save the bootsector built from the source + partition information 467 IN HANDLE RootPartition) // Partition holding the (old) FAT32 information 468 { 469 NTSTATUS Status; 470 UNICODE_STRING Name; 471 IO_STATUS_BLOCK IoStatusBlock; 472 LARGE_INTEGER FileOffset; 473 USHORT BackupBootSector = 0; 474 BOOTCODE OrigBootSector = {0}; 475 BOOTCODE NewBootSector = {0}; 476 477 /* Allocate and read the current original partition bootsector */ 478 Status = ReadBootCodeByHandle(&OrigBootSector, 479 RootPartition, 480 FAT32_BOOTSECTOR_SIZE); 481 if (!NT_SUCCESS(Status)) 482 return Status; 483 484 /* Allocate and read the new bootsector (2 sectors) from SrcPath */ 485 RtlInitUnicodeString(&Name, SrcPath); 486 Status = ReadBootCodeFromFile(&NewBootSector, 487 &Name, 488 2 * FAT32_BOOTSECTOR_SIZE); 489 if (!NT_SUCCESS(Status)) 490 { 491 FreeBootCode(&OrigBootSector); 492 return Status; 493 } 494 495 /* Adjust the bootsector (copy a part of the FAT32 BPB) */ 496 RtlCopyMemory(&((PFAT32_BOOTSECTOR)NewBootSector.BootCode)->OemName, 497 &((PFAT32_BOOTSECTOR)OrigBootSector.BootCode)->OemName, 498 FIELD_OFFSET(FAT32_BOOTSECTOR, BootCodeAndData) - 499 FIELD_OFFSET(FAT32_BOOTSECTOR, OemName)); 500 501 /* 502 * We know we copy the boot code to a file only when DstPath != RootPartition, 503 * otherwise the boot code is copied to the specified root partition. 504 */ 505 if (DstPath != RootPartition) 506 { 507 /* Copy to a file: Disable the backup bootsector */ 508 ((PFAT32_BOOTSECTOR)NewBootSector.BootCode)->BackupBootSector = 0; 509 } 510 else 511 { 512 /* Copy to a disk: Get the location of the backup bootsector */ 513 BackupBootSector = ((PFAT32_BOOTSECTOR)OrigBootSector.BootCode)->BackupBootSector; 514 } 515 516 /* Free the original bootsector */ 517 FreeBootCode(&OrigBootSector); 518 519 /* Write the first sector of the new bootcode to DstPath sector 0 */ 520 FileOffset.QuadPart = 0ULL; 521 Status = NtWriteFile(DstPath, 522 NULL, 523 NULL, 524 NULL, 525 &IoStatusBlock, 526 NewBootSector.BootCode, 527 FAT32_BOOTSECTOR_SIZE, 528 &FileOffset, 529 NULL); 530 if (!NT_SUCCESS(Status)) 531 { 532 DPRINT1("NtWriteFile() failed (Status %lx)\n", Status); 533 FreeBootCode(&NewBootSector); 534 return Status; 535 } 536 537 if (DstPath == RootPartition) 538 { 539 /* Copy to a disk: Write the backup bootsector */ 540 if ((BackupBootSector != 0x0000) && (BackupBootSector != 0xFFFF)) 541 { 542 FileOffset.QuadPart = (ULONGLONG)((ULONG)BackupBootSector * FAT32_BOOTSECTOR_SIZE); 543 Status = NtWriteFile(DstPath, 544 NULL, 545 NULL, 546 NULL, 547 &IoStatusBlock, 548 NewBootSector.BootCode, 549 FAT32_BOOTSECTOR_SIZE, 550 &FileOffset, 551 NULL); 552 if (!NT_SUCCESS(Status)) 553 { 554 DPRINT1("NtWriteFile() failed (Status %lx)\n", Status); 555 FreeBootCode(&NewBootSector); 556 return Status; 557 } 558 } 559 } 560 561 /* Write the second sector of the new bootcode to boot disk sector 14 */ 562 // FileOffset.QuadPart = (ULONGLONG)(14 * FAT32_BOOTSECTOR_SIZE); 563 FileOffset.QuadPart = 14 * FAT32_BOOTSECTOR_SIZE; 564 Status = NtWriteFile(DstPath, // or really RootPartition ??? 565 NULL, 566 NULL, 567 NULL, 568 &IoStatusBlock, 569 ((PUCHAR)NewBootSector.BootCode + FAT32_BOOTSECTOR_SIZE), 570 FAT32_BOOTSECTOR_SIZE, 571 &FileOffset, 572 NULL); 573 if (!NT_SUCCESS(Status)) 574 { 575 DPRINT1("NtWriteFile() failed (Status %lx)\n", Status); 576 } 577 578 /* Free the new bootsector */ 579 FreeBootCode(&NewBootSector); 580 581 return Status; 582 } 583 584 NTSTATUS 585 InstallBtrfsBootCode( 586 IN PCWSTR SrcPath, // BTRFS bootsector source file (on the installation medium) 587 IN HANDLE DstPath, // Where to save the bootsector built from the source + partition information 588 IN HANDLE RootPartition) // Partition holding the (old) BTRFS information 589 { 590 NTSTATUS Status; 591 NTSTATUS LockStatus; 592 UNICODE_STRING Name; 593 IO_STATUS_BLOCK IoStatusBlock; 594 LARGE_INTEGER FileOffset; 595 PARTITION_INFORMATION_EX PartInfo; 596 BOOTCODE NewBootSector = {0}; 597 598 /* Allocate and read the new bootsector from SrcPath */ 599 RtlInitUnicodeString(&Name, SrcPath); 600 Status = ReadBootCodeFromFile(&NewBootSector, 601 &Name, 602 BTRFS_BOOTSECTOR_SIZE); 603 if (!NT_SUCCESS(Status)) 604 return Status; 605 606 /* 607 * The BTRFS driver requires the volume to be locked in order to modify 608 * the first sectors of the partition, even though they are outside the 609 * file-system space / in the reserved area (they are situated before 610 * the super-block at 0x1000) and is in principle allowed by the NT 611 * storage stack. 612 * So we lock here in order to write the bootsector at sector 0. 613 * If locking fails, we ignore and continue nonetheless. 614 */ 615 LockStatus = NtFsControlFile(DstPath, 616 NULL, 617 NULL, 618 NULL, 619 &IoStatusBlock, 620 FSCTL_LOCK_VOLUME, 621 NULL, 622 0, 623 NULL, 624 0); 625 if (!NT_SUCCESS(LockStatus)) 626 { 627 DPRINT1("WARNING: Failed to lock BTRFS volume for writing bootsector! Operations may fail! (Status 0x%lx)\n", LockStatus); 628 } 629 630 /* Obtain partition info and write it to the bootsector */ 631 Status = NtDeviceIoControlFile(RootPartition, 632 NULL, 633 NULL, 634 NULL, 635 &IoStatusBlock, 636 IOCTL_DISK_GET_PARTITION_INFO_EX, 637 NULL, 638 0, 639 &PartInfo, 640 sizeof(PartInfo)); 641 if (!NT_SUCCESS(Status)) 642 { 643 DPRINT1("IOCTL_DISK_GET_PARTITION_INFO_EX failed (Status %lx)\n", Status); 644 goto Quit; 645 } 646 647 /* Write new bootsector to RootPath */ 648 ((PBTRFS_BOOTSECTOR)NewBootSector.BootCode)->PartitionStartLBA = 649 PartInfo.StartingOffset.QuadPart / SECTORSIZE; 650 651 /* Write sector 0 */ 652 FileOffset.QuadPart = 0ULL; 653 Status = NtWriteFile(DstPath, 654 NULL, 655 NULL, 656 NULL, 657 &IoStatusBlock, 658 NewBootSector.BootCode, 659 NewBootSector.Length, 660 &FileOffset, 661 NULL); 662 if (!NT_SUCCESS(Status)) 663 { 664 DPRINT1("NtWriteFile() failed (Status %lx)\n", Status); 665 goto Quit; 666 } 667 668 Quit: 669 /* Unlock the volume */ 670 LockStatus = NtFsControlFile(DstPath, 671 NULL, 672 NULL, 673 NULL, 674 &IoStatusBlock, 675 FSCTL_UNLOCK_VOLUME, 676 NULL, 677 0, 678 NULL, 679 0); 680 if (!NT_SUCCESS(LockStatus)) 681 { 682 DPRINT1("Failed to unlock BTRFS volume (Status 0x%lx)\n", LockStatus); 683 } 684 685 /* Free the new bootsector */ 686 FreeBootCode(&NewBootSector); 687 688 return Status; 689 } 690 691 NTSTATUS 692 InstallNtfsBootCode( 693 IN PCWSTR SrcPath, // NTFS bootsector source file (on the installation medium) 694 IN HANDLE DstPath, // Where to save the bootsector built from the source + partition information 695 IN HANDLE RootPartition) // Partition holding the (old) NTFS information 696 { 697 NTSTATUS Status; 698 UNICODE_STRING Name; 699 IO_STATUS_BLOCK IoStatusBlock; 700 LARGE_INTEGER FileOffset; 701 BOOTCODE OrigBootSector = {0}; 702 BOOTCODE NewBootSector = {0}; 703 704 /* Allocate and read the current original partition bootsector */ 705 Status = ReadBootCodeByHandle(&OrigBootSector, RootPartition, NTFS_BOOTSECTOR_SIZE); 706 if (!NT_SUCCESS(Status)) 707 { 708 DPRINT1("InstallNtfsBootCode: Status %lx\n", Status); 709 return Status; 710 } 711 712 /* Allocate and read the new bootsector (16 sectors) from SrcPath */ 713 RtlInitUnicodeString(&Name, SrcPath); 714 Status = ReadBootCodeFromFile(&NewBootSector, &Name, NTFS_BOOTSECTOR_SIZE); 715 if (!NT_SUCCESS(Status)) 716 { 717 DPRINT1("InstallNtfsBootCode: Status %lx\n", Status); 718 FreeBootCode(&OrigBootSector); 719 return Status; 720 } 721 722 /* Adjust the bootsector (copy a part of the NTFS BPB) */ 723 RtlCopyMemory(&((PNTFS_BOOTSECTOR)NewBootSector.BootCode)->OEMID, 724 &((PNTFS_BOOTSECTOR)OrigBootSector.BootCode)->OEMID, 725 FIELD_OFFSET(NTFS_BOOTSECTOR, BootStrap) - FIELD_OFFSET(NTFS_BOOTSECTOR, OEMID)); 726 727 /* Write sector 0 */ 728 FileOffset.QuadPart = 0ULL; 729 Status = NtWriteFile(DstPath, 730 NULL, 731 NULL, 732 NULL, 733 &IoStatusBlock, 734 NewBootSector.BootCode, 735 NewBootSector.Length, 736 &FileOffset, 737 NULL); 738 if (!NT_SUCCESS(Status)) 739 { 740 DPRINT1("NtWriteFile() failed (Status %lx)\n", Status); 741 goto Quit; 742 } 743 744 Quit: 745 /* Free the new bootsector */ 746 FreeBootCode(&NewBootSector); 747 748 return Status; 749 } 750 751 752 // 753 // Formatting routines 754 // 755 756 NTSTATUS 757 ChkdskVolume( 758 _In_ PVOLINFO Volume, 759 _In_ BOOLEAN FixErrors, 760 _In_ BOOLEAN Verbose, 761 _In_ BOOLEAN CheckOnlyIfDirty, 762 _In_ BOOLEAN ScanDrive, 763 _In_opt_ PFMIFSCALLBACK Callback) 764 { 765 /* Do not check a volume with an unknown file system */ 766 if (!*Volume->FileSystem) 767 return STATUS_UNRECOGNIZED_VOLUME; 768 769 /* Check the volume */ 770 DPRINT("Volume->DeviceName: %S\n", Volume->DeviceName); 771 return ChkdskFileSystem(Volume->DeviceName, 772 Volume->FileSystem, 773 FixErrors, 774 Verbose, 775 CheckOnlyIfDirty, 776 ScanDrive, 777 Callback); 778 } 779 780 NTSTATUS 781 ChkdskPartition( 782 _In_ PPARTENTRY PartEntry, 783 _In_ BOOLEAN FixErrors, 784 _In_ BOOLEAN Verbose, 785 _In_ BOOLEAN CheckOnlyIfDirty, 786 _In_ BOOLEAN ScanDrive, 787 _In_opt_ PFMIFSCALLBACK Callback) 788 { 789 ASSERT(PartEntry->IsPartitioned && PartEntry->PartitionNumber != 0); 790 ASSERT(PartEntry->Volume); 791 792 // if (!PartEntry->Volume) { check_raw_sectors(); } else { check_FS(); } 793 794 /* Check the associated volume */ 795 return ChkdskVolume(&PartEntry->Volume->Info, 796 FixErrors, 797 Verbose, 798 CheckOnlyIfDirty, 799 ScanDrive, 800 Callback); 801 } 802 803 NTSTATUS 804 FormatVolume( 805 _In_ PVOLINFO Volume, 806 _In_ PCWSTR FileSystemName, 807 _In_ FMIFS_MEDIA_FLAG MediaFlag, 808 _In_opt_ PCWSTR Label, 809 _In_ BOOLEAN QuickFormat, 810 _In_ ULONG ClusterSize, 811 _In_opt_ PFMIFSCALLBACK Callback) 812 { 813 NTSTATUS Status; 814 815 if (!FileSystemName || !*FileSystemName) 816 { 817 DPRINT1("No file system specified\n"); 818 return STATUS_UNRECOGNIZED_VOLUME; 819 } 820 821 /* Format the volume */ 822 DPRINT("Volume->DeviceName: %S\n", Volume->DeviceName); 823 Status = FormatFileSystem(Volume->DeviceName, 824 FileSystemName, 825 MediaFlag, 826 Label, 827 QuickFormat, 828 ClusterSize, 829 Callback); 830 if (!NT_SUCCESS(Status)) 831 return Status; 832 833 /* Set the new volume's file system and label */ 834 RtlStringCbCopyW(Volume->FileSystem, sizeof(Volume->FileSystem), FileSystemName); 835 if (!Label) Label = L""; 836 RtlStringCbCopyW(Volume->VolumeLabel, sizeof(Volume->VolumeLabel), Label); 837 838 return STATUS_SUCCESS; 839 } 840 841 NTSTATUS 842 FormatPartition( 843 _In_ PPARTENTRY PartEntry, 844 _In_ PCWSTR FileSystemName, 845 _In_ FMIFS_MEDIA_FLAG MediaFlag, 846 _In_opt_ PCWSTR Label, 847 _In_ BOOLEAN QuickFormat, 848 _In_ ULONG ClusterSize, 849 _In_opt_ PFMIFSCALLBACK Callback) 850 { 851 NTSTATUS Status; 852 PDISKENTRY DiskEntry = PartEntry->DiskEntry; 853 UCHAR PartitionType; 854 855 ASSERT(PartEntry->IsPartitioned && PartEntry->PartitionNumber != 0); 856 857 if (!FileSystemName || !*FileSystemName) 858 { 859 DPRINT1("No file system specified\n"); 860 return STATUS_UNRECOGNIZED_VOLUME; 861 } 862 863 /* 864 * Prepare the partition for formatting (for MBR disks, reset the 865 * partition type), and adjust the file system name in case of FAT 866 * vs. FAT32, depending on the geometry of the partition. 867 */ 868 869 // FIXME: Do this only if QuickFormat == FALSE? What about FAT handling? 870 871 /* 872 * Retrieve a partition type as a hint only. It will be used to determine 873 * whether to actually use FAT12/16 or FAT32 file system, depending on the 874 * geometry of the partition. If the partition resides on an MBR disk, 875 * the partition style will be reset to this value as well, unless the 876 * partition is OEM. 877 */ 878 PartitionType = FileSystemToMBRPartitionType(FileSystemName, 879 PartEntry->StartSector.QuadPart, 880 PartEntry->SectorCount.QuadPart); 881 if (PartitionType == PARTITION_ENTRY_UNUSED) 882 { 883 /* Unknown file system */ 884 DPRINT1("Unknown file system '%S'\n", FileSystemName); 885 return STATUS_UNRECOGNIZED_VOLUME; 886 } 887 888 /* Reset the MBR partition type, unless this is an OEM partition */ 889 if (DiskEntry->DiskStyle == PARTITION_STYLE_MBR) 890 { 891 if (!IsOEMPartition(PartEntry->PartitionType)) 892 SetMBRPartitionType(PartEntry, PartitionType); 893 } 894 895 /* 896 * Adjust the file system name in case of FAT vs. FAT32, according to 897 * the type of partition returned by FileSystemToMBRPartitionType(). 898 */ 899 if (_wcsicmp(FileSystemName, L"FAT") == 0) 900 { 901 if ((PartitionType == PARTITION_FAT32) || 902 (PartitionType == PARTITION_FAT32_XINT13)) 903 { 904 FileSystemName = L"FAT32"; 905 } 906 } 907 908 /* Commit the partition changes to the disk */ 909 Status = WritePartitions(DiskEntry); 910 if (!NT_SUCCESS(Status)) 911 { 912 DPRINT1("WritePartitions(disk %lu) failed, Status 0x%08lx\n", 913 DiskEntry->DiskNumber, Status); 914 return STATUS_PARTITION_FAILURE; 915 } 916 917 /* We must have an associated volume now */ 918 ASSERT(PartEntry->Volume); 919 920 /* Format the associated volume */ 921 Status = FormatVolume(&PartEntry->Volume->Info, 922 FileSystemName, 923 MediaFlag, 924 Label, 925 QuickFormat, 926 ClusterSize, 927 Callback); 928 if (!NT_SUCCESS(Status)) 929 return Status; 930 931 PartEntry->Volume->FormatState = Formatted; 932 PartEntry->Volume->New = FALSE; 933 return STATUS_SUCCESS; 934 } 935 936 937 // 938 // FileSystem Volume Operations Queue 939 // 940 941 static FSVOL_OP 942 DoFormatting( 943 _In_ PVOLENTRY Volume, 944 _In_opt_ PVOID Context, 945 _In_opt_ PFSVOL_CALLBACK FsVolCallback) 946 { 947 FSVOL_OP Result; 948 NTSTATUS Status = STATUS_SUCCESS; 949 PPARTENTRY PartEntry; 950 FORMAT_VOLUME_INFO FmtInfo = {0}; 951 952 PartEntry = Volume->PartEntry; 953 ASSERT(PartEntry && (PartEntry->Volume == Volume)); 954 955 FmtInfo.Volume = Volume; 956 957 RetryFormat: 958 Result = FsVolCallback(Context, 959 FSVOLNOTIFY_STARTFORMAT, 960 (ULONG_PTR)&FmtInfo, 961 FSVOL_FORMAT); 962 if (Result != FSVOL_DOIT) 963 goto EndFormat; 964 965 ASSERT(FmtInfo.FileSystemName && *FmtInfo.FileSystemName); 966 967 /* Format the partition */ 968 Status = FormatPartition(PartEntry, 969 FmtInfo.FileSystemName, 970 FmtInfo.MediaFlag, 971 FmtInfo.Label, 972 FmtInfo.QuickFormat, 973 FmtInfo.ClusterSize, 974 FmtInfo.Callback); 975 if (!NT_SUCCESS(Status)) 976 { 977 // FmtInfo.NtPathPartition = PathBuffer; 978 FmtInfo.ErrorStatus = Status; 979 980 Result = FsVolCallback(Context, 981 FSVOLNOTIFY_FORMATERROR, 982 (ULONG_PTR)&FmtInfo, 983 0); 984 if (Result == FSVOL_RETRY) 985 goto RetryFormat; 986 // else if (Result == FSVOL_ABORT || Result == FSVOL_SKIP), stop. 987 } 988 989 EndFormat: 990 /* This notification is always sent, even in case of error or abort */ 991 FmtInfo.ErrorStatus = Status; 992 FsVolCallback(Context, 993 FSVOLNOTIFY_ENDFORMAT, 994 (ULONG_PTR)&FmtInfo, 995 0); 996 return Result; 997 } 998 999 static FSVOL_OP 1000 DoChecking( 1001 _In_ PVOLENTRY Volume, 1002 _In_opt_ PVOID Context, 1003 _In_opt_ PFSVOL_CALLBACK FsVolCallback) 1004 { 1005 FSVOL_OP Result; 1006 NTSTATUS Status = STATUS_SUCCESS; 1007 CHECK_VOLUME_INFO ChkInfo = {0}; 1008 1009 ASSERT(*Volume->Info.FileSystem); 1010 1011 ChkInfo.Volume = Volume; 1012 1013 RetryCheck: 1014 Result = FsVolCallback(Context, 1015 FSVOLNOTIFY_STARTCHECK, 1016 (ULONG_PTR)&ChkInfo, 1017 FSVOL_CHECK); 1018 if (Result != FSVOL_DOIT) 1019 goto EndCheck; 1020 1021 /* Check the volume */ 1022 Status = ChkdskVolume(&Volume->Info, 1023 ChkInfo.FixErrors, 1024 ChkInfo.Verbose, 1025 ChkInfo.CheckOnlyIfDirty, 1026 ChkInfo.ScanDrive, 1027 ChkInfo.Callback); 1028 1029 /* If volume checking succeeded, or if it is not supported 1030 * with the current file system, disable checks on the volume */ 1031 if (NT_SUCCESS(Status) || (Status == STATUS_NOT_SUPPORTED)) 1032 Volume->NeedsCheck = FALSE; 1033 1034 if (!NT_SUCCESS(Status)) 1035 { 1036 // ChkInfo.NtPathPartition = PathBuffer; 1037 ChkInfo.ErrorStatus = Status; 1038 1039 Result = FsVolCallback(Context, 1040 FSVOLNOTIFY_CHECKERROR, 1041 (ULONG_PTR)&ChkInfo, 1042 0); 1043 if (Result == FSVOL_RETRY) 1044 goto RetryCheck; 1045 // else if (Result == FSVOL_ABORT || Result == FSVOL_SKIP), stop. 1046 1047 // Volume->NeedsCheck = FALSE; 1048 } 1049 1050 EndCheck: 1051 /* This notification is always sent, even in case of error or abort */ 1052 ChkInfo.ErrorStatus = Status; 1053 FsVolCallback(Context, 1054 FSVOLNOTIFY_ENDCHECK, 1055 (ULONG_PTR)&ChkInfo, 1056 0); 1057 return Result; 1058 } 1059 1060 static 1061 PVOLENTRY 1062 GetNextUnformattedVolume( 1063 _In_ PPARTLIST List, 1064 _In_opt_ PVOLENTRY Volume) 1065 { 1066 PLIST_ENTRY Entry; 1067 1068 for (;;) 1069 { 1070 /* If we have a current volume, get the next one, otherwise get the first */ 1071 Entry = (Volume ? &Volume->ListEntry : &List->VolumesList); 1072 Entry = Entry->Flink; 1073 1074 if (Entry == &List->VolumesList) 1075 return NULL; 1076 1077 Volume = CONTAINING_RECORD(Entry, VOLENTRY, ListEntry); 1078 if (Volume->New && (Volume->FormatState == Unformatted)) 1079 { 1080 /* Found a candidate, return it */ 1081 return Volume; 1082 } 1083 } 1084 } 1085 1086 BOOLEAN 1087 FsVolCommitOpsQueue( 1088 _In_ PPARTLIST PartitionList, 1089 _In_ PVOLENTRY SystemVolume, 1090 _In_ PVOLENTRY InstallVolume, 1091 _In_opt_ PFSVOL_CALLBACK FsVolCallback, 1092 _In_opt_ PVOID Context) 1093 { 1094 BOOLEAN Success = TRUE; // Suppose success originally. 1095 FSVOL_OP Result; 1096 PLIST_ENTRY Entry; 1097 PVOLENTRY Volume; 1098 1099 /* Machine state for the format step */ 1100 typedef enum _FORMATMACHINESTATE 1101 { 1102 Start, 1103 FormatSystemVolume, 1104 FormatInstallVolume, 1105 FormatOtherVolume, 1106 FormatDone 1107 } FORMATMACHINESTATE; 1108 FORMATMACHINESTATE FormatState, OldFormatState; 1109 static const PCSTR FormatStateNames[] = { 1110 "Start", 1111 "FormatSystemVolume", 1112 "FormatInstallVolume", 1113 "FormatOtherVolume", 1114 "FormatDone" 1115 }; 1116 1117 ASSERT(PartitionList && SystemVolume && InstallVolume); 1118 1119 /* Commit all partition changes to all the disks */ 1120 if (!WritePartitionsToDisk(PartitionList)) 1121 { 1122 DPRINT("WritePartitionsToDisk() failed\n"); 1123 /* Result = */ FsVolCallback(Context, 1124 FSVOLNOTIFY_PARTITIONERROR, 1125 STATUS_PARTITION_FAILURE, // FIXME 1126 0); 1127 return FALSE; 1128 } 1129 1130 // 1131 // FIXME: Should we do the following here, or in the caller? 1132 // 1133 /* 1134 * In all cases, whether or not we are going to perform a formatting, 1135 * we must perform a file system check of both the system and the 1136 * installation volumes. 1137 */ 1138 SystemVolume->NeedsCheck = TRUE; 1139 InstallVolume->NeedsCheck = TRUE; 1140 1141 Result = FsVolCallback(Context, 1142 FSVOLNOTIFY_STARTQUEUE, 1143 0, 0); 1144 if (Result == FSVOL_ABORT) 1145 return FALSE; 1146 1147 /* 1148 * Commit the Format queue 1149 */ 1150 1151 Result = FsVolCallback(Context, 1152 FSVOLNOTIFY_STARTSUBQUEUE, 1153 FSVOL_FORMAT, 1154 0); 1155 if (Result == FSVOL_ABORT) 1156 return FALSE; 1157 /** HACK!! **/ 1158 if (Result == FSVOL_SKIP) 1159 goto StartCheckQueue; 1160 /** END HACK!! **/ 1161 1162 /* Reset the formatter machine state */ 1163 FormatState = Start; 1164 Volume = NULL; 1165 NextFormat: 1166 OldFormatState = FormatState; 1167 switch (FormatState) 1168 { 1169 case Start: 1170 { 1171 /* 1172 * We start by formatting the system volume in case it is new 1173 * (it didn't exist before) and is not the same as the installation 1174 * volume. Otherwise we just require a file system check on it, 1175 * and start by formatting the installation volume instead. 1176 */ 1177 if (SystemVolume != InstallVolume) 1178 { 1179 Volume = SystemVolume; 1180 1181 if (Volume->FormatState == Unformatted) 1182 { 1183 // TODO: Should we let the user use a custom file system, 1184 // or should we always use FAT(32) for it? 1185 // For "compatibility", FAT(32) would be best indeed. 1186 1187 FormatState = FormatSystemVolume; 1188 DPRINT1("FormatState: %s --> %s\n", 1189 FormatStateNames[OldFormatState], FormatStateNames[FormatState]); 1190 break; 1191 } 1192 1193 /* The system volume is separate, so it had better be formatted! */ 1194 ASSERT(Volume->FormatState == Formatted); 1195 1196 /* Require a file system check on the system volume too */ 1197 Volume->NeedsCheck = TRUE; 1198 } 1199 __fallthrough; 1200 } 1201 1202 case FormatSystemVolume: 1203 { 1204 Volume = InstallVolume; 1205 1206 FormatState = FormatInstallVolume; 1207 DPRINT1("FormatState: %s --> %s\n", 1208 FormatStateNames[OldFormatState], FormatStateNames[FormatState]); 1209 break; 1210 } 1211 1212 case FormatInstallVolume: 1213 /* Restart volume enumeration */ 1214 Volume = NULL; 1215 case FormatOtherVolume: 1216 { 1217 Volume = GetNextUnformattedVolume(PartitionList, Volume); 1218 1219 FormatState = (Volume ? FormatOtherVolume : FormatDone); 1220 DPRINT1("FormatState: %s --> %s\n", 1221 FormatStateNames[OldFormatState], FormatStateNames[FormatState]); 1222 if (Volume) 1223 break; 1224 __fallthrough; 1225 } 1226 1227 case FormatDone: 1228 { 1229 DPRINT1("FormatState: FormatDone\n"); 1230 Success = TRUE; 1231 goto EndFormat; 1232 } 1233 DEFAULT_UNREACHABLE; 1234 } 1235 1236 Result = DoFormatting(Volume, Context, FsVolCallback); 1237 if (Result == FSVOL_ABORT) 1238 { 1239 Success = FALSE; 1240 goto Quit; 1241 } 1242 /* Schedule a check for this volume */ 1243 Volume->NeedsCheck = TRUE; 1244 /* Go to the next volume to be formatted */ 1245 goto NextFormat; 1246 1247 EndFormat: 1248 FsVolCallback(Context, 1249 FSVOLNOTIFY_ENDSUBQUEUE, 1250 FSVOL_FORMAT, 1251 0); 1252 1253 1254 /* 1255 * Commit the CheckFS queue 1256 */ 1257 1258 StartCheckQueue: 1259 Result = FsVolCallback(Context, 1260 FSVOLNOTIFY_STARTSUBQUEUE, 1261 FSVOL_CHECK, 1262 0); 1263 if (Result == FSVOL_ABORT) 1264 return FALSE; 1265 1266 /* Loop through each unchecked volume and do the check */ 1267 for (Entry = PartitionList->VolumesList.Flink; 1268 Entry != &PartitionList->VolumesList; 1269 Entry = Entry->Flink) 1270 { 1271 Volume = CONTAINING_RECORD(Entry, VOLENTRY, ListEntry); 1272 if (!Volume->NeedsCheck) 1273 continue; 1274 1275 /* Found a candidate */ 1276 ASSERT(Volume->FormatState == Formatted); 1277 Result = DoChecking(Volume, Context, FsVolCallback); 1278 if (Result == FSVOL_ABORT) 1279 { 1280 Success = FALSE; 1281 goto Quit; 1282 } 1283 /* Go to the next volume to be checked */ 1284 } 1285 Success = TRUE; 1286 1287 FsVolCallback(Context, 1288 FSVOLNOTIFY_ENDSUBQUEUE, 1289 FSVOL_CHECK, 1290 0); 1291 1292 1293 Quit: 1294 /* All the queues have been committed */ 1295 FsVolCallback(Context, 1296 FSVOLNOTIFY_ENDQUEUE, 1297 Success, 1298 0); 1299 return Success; 1300 } 1301 1302 /* EOF */ 1303