#include "spprecmp.h" #pragma hdrstop // // This variable is needed since it contains a buffer that can // be used in kernel mode. The buffer is used by NtFsControlFile, // since the Zw API is not exported // extern PSETUP_COMMUNICATION CommunicationParams; #define VERIFY_SIZE 65536 typedef struct { UCHAR IntelNearJumpCommand[1]; // Intel Jump command UCHAR BootStrapJumpOffset[2]; // offset of boot strap code UCHAR OemData[8]; // OEM data UCHAR BytesPerSector[2]; // BPB UCHAR SectorsPerCluster[1]; // UCHAR ReservedSectors[2]; // UCHAR Fats[1]; // UCHAR RootEntries[2]; // UCHAR Sectors[2]; // UCHAR Media[1]; // UCHAR SectorsPerFat[2]; // UCHAR SectorsPerTrack[2]; // UCHAR Heads[2]; // UCHAR HiddenSectors[4]; // UCHAR LargeSectors[4]; // UCHAR PhysicalDrive[1]; // 0 = removable, 80h = fixed UCHAR CurrentHead[1]; // not used by fs utils UCHAR Signature[1]; // boot signature UCHAR SerialNumber[4]; // serial number UCHAR Label[11]; // volume label, aligned padded UCHAR SystemIdText[8]; // system ID, FAT for example } UNALIGNED_SECTOR_ZERO, *PUNALIGNED_SECTOR_ZERO; #define CSEC_FAT32MEG 65536 #define CSEC_FAT16BIT 32680 #define MIN_CLUS_BIG 4085 // Minimum clusters for a big FAT. #define MAX_CLUS_BIG 65525 // Maximum + 1 clusters for big FAT. USHORT ComputeSecPerCluster( IN ULONG NumSectors, IN BOOLEAN SmallFat ) /*++ Routine Description: This routine computes the number of sectors per cluster. Arguments: NumSectors - Supplies the number of sectors on the disk. SmallFat - Supplies whether or not the FAT should be small. Return Value: The number of sectors per cluster necessary. --*/ { ULONG threshold; USHORT sec_per_clus; USHORT min_sec_per_clus; threshold = SmallFat ? MIN_CLUS_BIG : MAX_CLUS_BIG; sec_per_clus = 1; while (NumSectors >= threshold) { sec_per_clus *= 2; threshold *= 2; } if (SmallFat) { min_sec_per_clus = 8; } else { min_sec_per_clus = 4; } return max(sec_per_clus, min_sec_per_clus); } ULONG SpComputeSerialNumber( VOID ) /*++ Routine Description: This routine computes a new serial number for a volume. Arguments: Seed - Supplies a seed for the serial number. Return Value: A new volume serial number. --*/ { PUCHAR p; ULONG i; TIME_FIELDS time_fields; static ULONG Seed = 0; ULONG SerialNumber; BOOLEAN b; // // If this is the first time we've entered this routine, // generate a seed value based on the real time clock. // if(!Seed) { b = HalQueryRealTimeClock(&time_fields); ASSERT(b); Seed = ((time_fields.Year - 1970) *366*24*60*60) + (time_fields.Month *31*24*60*60) + (time_fields.Day *24*60*60) + (time_fields.Hour *60*60) + (time_fields.Minute *60) + time_fields.Second + ((ULONG)time_fields.Milliseconds << 10); ASSERT(Seed); if(!Seed) { Seed = 1; } } SerialNumber = Seed; p = (PUCHAR)&SerialNumber; for(i=0; i> 2) + (SerialNumber << 30); } if(++Seed == 0) { // unlikely, but possible. Seed++; } return SerialNumber; } VOID EditFat( IN USHORT ClusterNumber, IN USHORT ClusterEntry, IN OUT PUCHAR Fat, IN BOOLEAN SmallFat ) /*++ Routine Description: This routine edits the FAT entry 'ClusterNumber' with 'ClusterEntry'. Arguments: ClusterNumber - Supplies the number of the cluster to edit. ClusterEntry - Supplies the new value for that cluster number. Fat - Supplies the FAT to edit. SmallFat - Supplies whether or not the FAT is small. Return Value: None. --*/ { ULONG n; if (SmallFat) { n = ClusterNumber*3; if (n%2) { Fat[n/2] = (UCHAR) ((Fat[n/2]&0x0F) | ((ClusterEntry&0x000F)<<4)); Fat[n/2 + 1] = (UCHAR) ((ClusterEntry&0x0FF0)>>4); } else { Fat[n/2] = (UCHAR) (ClusterEntry&0x00FF); Fat[n/2 + 1] = (UCHAR) ((Fat[n/2 + 1]&0xF0) | ((ClusterEntry&0x0F00)>>8)); } } else { ((PUSHORT) Fat)[ClusterNumber] = ClusterEntry; } } NTSTATUS FmtFillFormatBuffer( IN ULONGLONG NumberOfSectors, IN ULONG SectorSize, IN ULONG SectorsPerTrack, IN ULONG NumberOfHeads, IN ULONGLONG NumberOfHiddenSectors, OUT PVOID FormatBuffer, IN ULONG FormatBufferSize, OUT PULONGLONG SuperAreaSize, IN PULONG BadSectorsList, IN ULONG NumberOfBadSectors, OUT PUCHAR SystemId ) /*++ Routine Description: This routine computes a FAT super area based on the disk size, disk geometry, and bad sectors of the volume. Arguments: NumberOfSectors - Supplies the number of sectors on the volume. SectorSize - Supplies the number of bytes per sector. SectorsPerTrack - Supplies the number of sectors per track. NumberOfHeads - Supplies the number of heads. NumberOfHiddenSectors - Supplies the number of hidden sectors. FormatBuffer - Returns the super area for the volume. FormatBufferSize - Supplies the number of bytes in the supplied buffer. SuperAreaSize - Returns the number of bytes in the super area. BadSectorsList - Supplies the list of bad sectors on the volume. NumberOfBadSectors - Supplies the number of bad sectors in the list. Return Value: ENOMEM - The buffer wasn't big enough. E2BIG - The disk is too large to be formatted. EIO - There is a bad sector in the super area. EINVAL - There is a bad sector off the end of the disk. ESUCCESS --*/ { PUNALIGNED_SECTOR_ZERO psecz; PUCHAR puchar; USHORT tmp_ushort; ULONG tmp_ulong; BOOLEAN small_fat; ULONG num_sectors; UCHAR partition_id; ULONG sec_per_fat; ULONG sec_per_root; ULONG sec_per_clus; ULONG i; ULONG sec_per_sa; RtlZeroMemory(FormatBuffer,FormatBufferSize); // Make sure that there's enough room for the BPB. if(!FormatBuffer || FormatBufferSize < SectorSize) { return(STATUS_BUFFER_TOO_SMALL); } // Compute the number of sectors on disk. num_sectors = (ULONG)NumberOfSectors; // Compute the partition identifier. partition_id = num_sectors < CSEC_FAT16BIT ? PARTITION_FAT_12 : num_sectors < CSEC_FAT32MEG ? PARTITION_FAT_16 : PARTITION_HUGE; // Compute whether or not to have a big or small FAT. small_fat = (BOOLEAN) (partition_id == PARTITION_FAT_12); psecz = (PUNALIGNED_SECTOR_ZERO) FormatBuffer; puchar = (PUCHAR) FormatBuffer; // // Copy the fat boot code into the format buffer. // if (!IsNEC_98) { //NEC98 ASSERT(sizeof(FatBootCode) == 512); RtlMoveMemory(psecz,FatBootCode,sizeof(FatBootCode)); // Set up the jump instruction. psecz->IntelNearJumpCommand[0] = 0xeb; psecz->IntelNearJumpCommand[1] = 0x3c; psecz->IntelNearJumpCommand[2] = 0x90; } else { ASSERT(sizeof(PC98FatBootCode) == 512); RtlMoveMemory(psecz,PC98FatBootCode,sizeof(PC98FatBootCode)); // // Already written jump instruction to bootcode. // So,do not reset jump code. // } //NEC98 // Set up the OEM data. memcpy(psecz->OemData, "MSDOS5.0", 8); // Set up the bytes per sector. U_USHORT(psecz->BytesPerSector) = (USHORT)SectorSize; // Set up the number of sectors per cluster. sec_per_clus = ComputeSecPerCluster(num_sectors, small_fat); if (sec_per_clus > 128) { // The disk is too large to be formatted. return(STATUS_INVALID_PARAMETER); } psecz->SectorsPerCluster[0] = (UCHAR) sec_per_clus; // Set up the number of reserved sectors. U_USHORT(psecz->ReservedSectors) = (USHORT)max(1,512/SectorSize); // Set up the number of FATs. psecz->Fats[0] = 2; // Set up the number of root entries and number of sectors for the root. U_USHORT(psecz->RootEntries) = 512; sec_per_root = (512*32 - 1)/SectorSize + 1; // Set up the number of sectors. if (num_sectors >= 1<<16) { tmp_ushort = 0; tmp_ulong = num_sectors; } else { tmp_ushort = (USHORT) num_sectors; tmp_ulong = 0; } U_USHORT(psecz->Sectors) = tmp_ushort; U_ULONG(psecz->LargeSectors) = tmp_ulong; // Set up the media byte. psecz->Media[0] = 0xF8; // Set up the number of sectors per FAT. if (small_fat) { sec_per_fat = num_sectors/(2 + SectorSize*sec_per_clus*2/3); } else { sec_per_fat = num_sectors/(2 + SectorSize*sec_per_clus/2); } sec_per_fat++; U_USHORT(psecz->SectorsPerFat) = (USHORT)sec_per_fat; // Set up the number of sectors per track. U_USHORT(psecz->SectorsPerTrack) = (USHORT)SectorsPerTrack; // Set up the number of heads. U_USHORT(psecz->Heads) = (USHORT)NumberOfHeads; // Set up the number of hidden sectors. U_ULONG(psecz->HiddenSectors) = (ULONG)NumberOfHiddenSectors; // Set up the physical drive number. psecz->PhysicalDrive[0] = 0x80; psecz->CurrentHead[0] = 0; // Set up the BPB signature. psecz->Signature[0] = 0x29; // Set up the serial number. U_ULONG(psecz->SerialNumber) = SpComputeSerialNumber(); // Set up the volume label. memcpy(psecz->Label, "NO NAME ",11); // Set up the system id. memcpy(psecz->SystemIdText, small_fat ? "FAT12 " : "FAT16 ", 8); // Set up the boot signature. puchar[510] = 0x55; puchar[511] = 0xAA; // Now make sure that the buffer has enough room for both of the // FATs and the root directory. sec_per_sa = 1 + 2*sec_per_fat + sec_per_root; *SuperAreaSize = SectorSize*sec_per_sa; if (*SuperAreaSize > FormatBufferSize) { return(STATUS_BUFFER_TOO_SMALL); } // Set up the first FAT. puchar[SectorSize] = 0xF8; puchar[SectorSize + 1] = 0xFF; puchar[SectorSize + 2] = 0xFF; if (!small_fat) { puchar[SectorSize + 3] = 0xFF; } for (i = 0; i < NumberOfBadSectors; i++) { if (BadSectorsList[i] < sec_per_sa) { // There's a bad sector in the super area. return(STATUS_UNSUCCESSFUL); } if (BadSectorsList[i] >= num_sectors) { // Bad sector out of range. return(STATUS_NONEXISTENT_SECTOR); } // Compute the bad cluster number; tmp_ushort = (USHORT) ((BadSectorsList[i] - sec_per_sa)/sec_per_clus + 2); EditFat(tmp_ushort, (USHORT) 0xFFF7, &puchar[SectorSize], small_fat); } // Copy the first FAT onto the second. memcpy(&puchar[SectorSize*(1 + sec_per_fat)], &puchar[SectorSize], (unsigned int) SectorSize*sec_per_fat); *SystemId = partition_id; return(STATUS_SUCCESS); } VOID FmtVerifySectors( IN HANDLE Handle, IN ULONG NumberOfSectors, IN ULONG SectorSize, OUT PULONG* BadSectorsList, OUT PULONG NumberOfBadSectors ) /*++ Routine Description: This routine verifies all of the sectors on the volume. It returns a pointer to a list of bad sectors. The pointer will be NULL if there was an error detected. Arguments: Handle - Supplies a handle to the partition for verifying. NumberOfSectors - Supplies the number of partition sectors. SectorSize - Supplies the number of bytes per sector. BadSectorsList - Returns the list of bad sectors. NumberOfBadSectors - Returns the number of bad sectors in the list. Return Value: None. --*/ { ULONG num_read_sec; ULONG i, j; PULONG bad_sec_buf; ULONG max_num_bad; PVOID Gauge; IO_STATUS_BLOCK IoStatusBlock; NTSTATUS Status; VERIFY_INFORMATION VerifyInfo; max_num_bad = 100; bad_sec_buf = SpMemAlloc(max_num_bad*sizeof(ULONG)); ASSERT(bad_sec_buf); *NumberOfBadSectors = 0; num_read_sec = VERIFY_SIZE/SectorSize; // // Initialize the Gas Gauge // SpFormatMessage( TemporaryBuffer, sizeof(TemporaryBuffer), SP_TEXT_SETUP_IS_FORMATTING ); Gauge = SpCreateAndDisplayGauge( NumberOfSectors/num_read_sec, 0, VideoVars.ScreenHeight - STATUS_HEIGHT - (3*GAUGE_HEIGHT/2), TemporaryBuffer, NULL, GF_PERCENTAGE, 0 ); VerifyInfo.StartingOffset.QuadPart = 0; for (i = 0; i < NumberOfSectors; i += num_read_sec) { if (i + num_read_sec > NumberOfSectors) { num_read_sec = NumberOfSectors - i; } // // Verify this many sectors at the current offset. // VerifyInfo.Length = num_read_sec * SectorSize; Status = ZwDeviceIoControlFile( Handle, NULL, NULL, NULL, &IoStatusBlock, IOCTL_DISK_VERIFY, &VerifyInfo, sizeof(VerifyInfo), NULL, 0 ); // // I/O should be synchronous. // ASSERT(Status != STATUS_PENDING); if(!NT_SUCCESS(Status)) { // // Range is bad -- verify individual sectors. // VerifyInfo.Length = SectorSize; for (j = 0; j < num_read_sec; j++) { Status = ZwDeviceIoControlFile( Handle, NULL, NULL, NULL, &IoStatusBlock, IOCTL_DISK_VERIFY, &VerifyInfo, sizeof(VerifyInfo), NULL, 0 ); ASSERT(Status != STATUS_PENDING); if(!NT_SUCCESS(Status)) { if (*NumberOfBadSectors == max_num_bad) { max_num_bad += 100; bad_sec_buf = SpMemRealloc( bad_sec_buf, max_num_bad*sizeof(ULONG) ); ASSERT(bad_sec_buf); } bad_sec_buf[(*NumberOfBadSectors)++] = i + j; } // // Advance to next sector. // VerifyInfo.StartingOffset.QuadPart += SectorSize; } } else { // // Advance to next range of sectors. // VerifyInfo.StartingOffset.QuadPart += VerifyInfo.Length; } if(Gauge) { SpTickGauge(Gauge); } } if(Gauge) { SpTickGauge(Gauge); } *BadSectorsList = bad_sec_buf; //return(STATUS_SUCCESS); } #if 0 // // Code not used, we call autoformat // NTSTATUS SpFatFormat( IN PDISK_REGION Region ) /*++ Routine Description: This routine does a FAT format on the given partition. The caller should have cleared the screen and displayed any message in the upper portion; this routine will maintain the gas gauge in the lower portion of the screen. Arguments: Region - supplies the disk region descriptor for the partition to be formatted. Return Value: --*/ { ULONG hidden_sectors; PULONG bad_sectors; ULONG num_bad_sectors; PVOID format_buffer; PVOID unaligned_format_buffer; ULONG max_sec_per_sa; ULONG super_area_size; PHARD_DISK pHardDisk; ULONG PartitionOrdinal; NTSTATUS Status; HANDLE Handle; ULONG BytesPerSector; IO_STATUS_BLOCK IoStatusBlock; LARGE_INTEGER LargeZero; UCHAR SysId; ULONG ActualSectorCount; SET_PARTITION_INFORMATION PartitionInfo; ASSERT(Region->PartitionedSpace); ASSERT(Region->TablePosition < PTABLE_DIMENSION); ASSERT(Region->Filesystem != FilesystemDoubleSpace); pHardDisk = &HardDisks[Region->DiskNumber]; BytesPerSector = pHardDisk->Geometry.BytesPerSector; PartitionOrdinal = SpPtGetOrdinal(Region,PartitionOrdinalCurrent); // // Make SURE it's not partition0! The results of formatting partition0 // are so disasterous that theis warrants a special check. // if(!PartitionOrdinal) { SpBugCheck( SETUP_BUGCHECK_PARTITION, PARTITIONBUG_B, Region->DiskNumber, 0 ); } #if defined(_AMD64_) || defined(_X86_) // // If we're going to format C:, then clear the previous OS entry // in boot.ini. // if(Region == SpPtValidSystemPartition()) { *OldSystemLine = '\0'; } #endif // defined(_AMD64_) || defined(_X86_) // // Query the number of hidden sectors and the actual number // of sectors in the volume. // SpPtGetSectorLayoutInformation(Region,&hidden_sectors,&ActualSectorCount); // // Open the partition for read/write access. // This shouldn't lock the volume so we need to lock it below. // Status = SpOpenPartition( pHardDisk->DevicePath, PartitionOrdinal, &Handle, TRUE ); if(!NT_SUCCESS(Status)) { KdPrintEx((DPFLTR_SETUP_ID, DPFLTR_ERROR_LEVEL, "SETUP: SpFatFormat: unable to open %ws partition %u (%lx)\n", pHardDisk->DevicePath, PartitionOrdinal, Status )); return(Status); } // // Lock the drive // Status = SpLockUnlockVolume( Handle, TRUE ); // // We shouldn't have any file opened that would cause this volume // to already be locked, so if we get failure (ie, STATUS_ACCESS_DENIED) // something is really wrong. This typically indicates something is // wrong with the hard disk that won't allow us to access it. // if(!NT_SUCCESS(Status)) { KdPrintEx((DPFLTR_SETUP_ID, DPFLTR_ERROR_LEVEL, "SETUP: SpFatFormat: status %lx, unable to lock drive \n",Status)); ZwClose(Handle); return(Status); } bad_sectors = NULL; FmtVerifySectors( Handle, ActualSectorCount, BytesPerSector, &bad_sectors, &num_bad_sectors ); max_sec_per_sa = 1 + 2*((2*65536 - 1)/BytesPerSector + 1) + ((512*32 - 1)/BytesPerSector + 1); unaligned_format_buffer = SpMemAlloc(max_sec_per_sa*BytesPerSector); ASSERT(unaligned_format_buffer); format_buffer = ALIGN(unaligned_format_buffer,BytesPerSector); Status = FmtFillFormatBuffer( ActualSectorCount, BytesPerSector, pHardDisk->Geometry.SectorsPerTrack, pHardDisk->Geometry.TracksPerCylinder, hidden_sectors, format_buffer, max_sec_per_sa*BytesPerSector, &super_area_size, bad_sectors, num_bad_sectors, &SysId ); if(bad_sectors) { SpMemFree(bad_sectors); } if(!NT_SUCCESS(Status)) { KdPrintEx((DPFLTR_SETUP_ID, DPFLTR_ERROR_LEVEL, "SETUP: SpFatFormat: status %lx from FmtFillFormatBuffer\n",Status)); SpLockUnlockVolume( Handle, FALSE ); ZwClose(Handle); SpMemFree(unaligned_format_buffer); return(Status); } // // Write the super area. // LargeZero.QuadPart = 0; Status = ZwWriteFile( Handle, NULL, NULL, NULL, &IoStatusBlock, format_buffer, super_area_size, &LargeZero, NULL ); // // I/O should be synchronous. // ASSERT(Status != STATUS_PENDING); SpMemFree(unaligned_format_buffer); if(!NT_SUCCESS(Status)) { KdPrintEx((DPFLTR_SETUP_ID, DPFLTR_ERROR_LEVEL, "SETUP: SpFatFormat: status %lx from ZwWriteFile\n",Status)); SpLockUnlockVolume( Handle, FALSE ); ZwClose(Handle); return(Status); } // // If we wrote the super area then the drive is now FAT! // If we don't change, say, a type of ntfs to fat, then code // that lays down the amd64/x86 boot code (*\bootini.c) will // come along and write 16 sectors of NTFS boot code into // sector 0 of our nice FAT volume -- very bad! // Preserve the filesystem type of FilesystemNewlyCreated // since other code later in setup relies on this. // if(Region->Filesystem >= FilesystemFirstKnown) { Region->Filesystem = FilesystemFat; } // // Set the partition type. // PartitionInfo.PartitionType = SysId; Status = ZwDeviceIoControlFile( Handle, NULL, NULL, NULL, &IoStatusBlock, IOCTL_DISK_SET_PARTITION_INFO, &PartitionInfo, sizeof(PartitionInfo), NULL, 0 ); if(!NT_SUCCESS(Status)) { KdPrintEx((DPFLTR_SETUP_ID, DPFLTR_ERROR_LEVEL, "SETUP: unable to set partition type (status = %lx)\n",Status)); } // // Dismount the drive // Status = SpDismountVolume( Handle ); if(!NT_SUCCESS(Status)) { KdPrintEx((DPFLTR_SETUP_ID, DPFLTR_ERROR_LEVEL, "SETUP: SpFatFormat: status %lx, unable to dismount drive\n",Status)); SpLockUnlockVolume( Handle, FALSE ); ZwClose(Handle); return(Status); } // // Unlock the drive // Status = SpLockUnlockVolume( Handle, FALSE ); if(!NT_SUCCESS(Status)) { KdPrintEx((DPFLTR_SETUP_ID, DPFLTR_ERROR_LEVEL, "SETUP: SpFatFormat: status %lx, unable to unlock drive\n",Status)); } ZwClose(Handle); return(Status); } #endif