// // Copyright (c) 2008-2011, Kenneth Bell // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. // namespace DiscUtils { using System; using System.Globalization; /// /// Class whose instances represent disk geometries. /// /// Instances of this class are immutable. public sealed class Geometry { private int _cylinders; private int _headsPerCylinder; private int _sectorsPerTrack; private int _bytesPerSector; /// /// Initializes a new instance of the Geometry class. The default 512 bytes per sector is assumed. /// /// The number of cylinders of the disk. /// The number of heads (aka platters) of the disk. /// The number of sectors per track/cylinder of the disk. public Geometry(int cylinders, int headsPerCylinder, int sectorsPerTrack) { _cylinders = cylinders; _headsPerCylinder = headsPerCylinder; _sectorsPerTrack = sectorsPerTrack; _bytesPerSector = 512; } /// /// Initializes a new instance of the Geometry class. /// /// The number of cylinders of the disk. /// The number of heads (aka platters) of the disk. /// The number of sectors per track/cylinder of the disk. /// The number of bytes per sector of the disk. public Geometry(int cylinders, int headsPerCylinder, int sectorsPerTrack, int bytesPerSector) { _cylinders = cylinders; _headsPerCylinder = headsPerCylinder; _sectorsPerTrack = sectorsPerTrack; _bytesPerSector = bytesPerSector; } /// /// Initializes a new instance of the Geometry class. /// /// The total capacity of the disk. /// The number of heads (aka platters) of the disk. /// The number of sectors per track/cylinder of the disk. /// The number of bytes per sector of the disk. public Geometry(long capacity, int headsPerCylinder, int sectorsPerTrack, int bytesPerSector) { _cylinders = (int)(capacity / (headsPerCylinder * (long)sectorsPerTrack * bytesPerSector)); _headsPerCylinder = headsPerCylinder; _sectorsPerTrack = sectorsPerTrack; _bytesPerSector = bytesPerSector; } /// /// Gets a null geometry, which has 512-byte sectors but zero sectors, tracks or cylinders. /// public static Geometry Null { get { return new Geometry(0, 0, 0, 512); } } /// /// Gets the number of cylinders. /// public int Cylinders { get { return _cylinders; } } /// /// Gets the number of heads (aka platters). /// public int HeadsPerCylinder { get { return _headsPerCylinder; } } /// /// Gets the number of sectors per track. /// public int SectorsPerTrack { get { return _sectorsPerTrack; } } /// /// Gets the number of bytes in each sector. /// public int BytesPerSector { get { return _bytesPerSector; } } /// /// Gets the total size of the disk (in sectors). /// [Obsolete("Use TotalSectorsLong instead, to support very large disks.")] public int TotalSectors { get { return Cylinders * HeadsPerCylinder * SectorsPerTrack; } } /// /// Gets the total size of the disk (in sectors). /// public long TotalSectorsLong { get { return (long)Cylinders * (long)HeadsPerCylinder * (long)SectorsPerTrack; } } /// /// Gets the total capacity of the disk (in bytes). /// public long Capacity { get { return TotalSectorsLong * (long)BytesPerSector; } } /// /// Gets the address of the last sector on the disk. /// public ChsAddress LastSector { get { return new ChsAddress(_cylinders - 1, _headsPerCylinder - 1, _sectorsPerTrack); } } /// /// Gets a value indicating whether the Geometry is consistent with the values a BIOS can support. /// public bool IsBiosSafe { get { return _cylinders <= 1024 && _headsPerCylinder <= 255 && _sectorsPerTrack <= 63; } } /// /// Gets a value indicating whether the Geometry is consistent with the values IDE can represent. /// public bool IsIdeSafe { get { return _cylinders <= 65536 && _headsPerCylinder <= 16 && _sectorsPerTrack <= 255; } } /// /// Gets a value indicating whether the Geometry is representable both by the BIOS and by IDE. /// public bool IsBiosAndIdeSafe { get { return _cylinders <= 1024 && _headsPerCylinder <= 16 && _sectorsPerTrack <= 63; } } /// /// Gets the 'Large' BIOS geometry for a disk, given it's physical geometry. /// /// The physical (aka IDE) geometry of the disk. /// The geometry a BIOS using the 'Large' method for calculating disk geometry will indicate for the disk. public static Geometry LargeBiosGeometry(Geometry ideGeometry) { int cylinders = ideGeometry.Cylinders; int heads = ideGeometry.HeadsPerCylinder; int sectors = ideGeometry.SectorsPerTrack; while (cylinders > 1024 && heads <= 127) { cylinders >>= 1; heads <<= 1; } return new Geometry(cylinders, heads, sectors); } /// /// Gets the 'LBA Assisted' BIOS geometry for a disk, given it's capacity. /// /// The capacity of the disk. /// The geometry a BIOS using the 'LBA Assisted' method for calculating disk geometry will indicate for the disk. public static Geometry LbaAssistedBiosGeometry(long capacity) { int heads; if (capacity <= 504 * Sizes.OneMiB) { heads = 16; } else if (capacity <= 1008 * Sizes.OneMiB) { heads = 32; } else if (capacity <= 2016 * Sizes.OneMiB) { heads = 64; } else if (capacity <= 4032 * Sizes.OneMiB) { heads = 128; } else { heads = 255; } int sectors = 63; int cylinders = (int)Math.Min(1024, capacity / (sectors * (long)heads * Sizes.Sector)); return new Geometry(cylinders, heads, sectors, Sizes.Sector); } /// /// Converts a geometry into one that is BIOS-safe, if not already. /// /// The geometry to make BIOS-safe. /// The capacity of the disk. /// The new geometry. /// This method returns the LBA-Assisted geometry if the given geometry isn't BIOS-safe. public static Geometry MakeBiosSafe(Geometry geometry, long capacity) { if (geometry == null) { return LbaAssistedBiosGeometry(capacity); } else if (geometry.IsBiosSafe) { return geometry; } else { return LbaAssistedBiosGeometry(capacity); } } /// /// Calculates a sensible disk geometry for a disk capacity using the VHD algorithm (errs under). /// /// The desired capacity of the disk. /// The appropriate disk geometry. /// The geometry returned tends to produce a disk with less capacity /// than requested (an exact capacity is not always possible). The geometry returned is the IDE /// (aka Physical) geometry of the disk, not necessarily the geometry used by the BIOS. public static Geometry FromCapacity(long capacity) { return FromCapacity(capacity, Utilities.SectorSize); } /// /// Calculates a sensible disk geometry for a disk capacity using the VHD algorithm (errs under). /// /// The desired capacity of the disk. /// The logical sector size of the disk. /// The appropriate disk geometry. /// The geometry returned tends to produce a disk with less capacity /// than requested (an exact capacity is not always possible). The geometry returned is the IDE /// (aka Physical) geometry of the disk, not necessarily the geometry used by the BIOS. public static Geometry FromCapacity(long capacity, int sectorSize) { int totalSectors; int cylinders; int headsPerCylinder; int sectorsPerTrack; // If more than ~128GB truncate at ~128GB if (capacity > 65535 * (long)16 * 255 * sectorSize) { totalSectors = 65535 * 16 * 255; } else { totalSectors = (int)(capacity / sectorSize); } // If more than ~32GB, break partition table compatibility. // Partition table has max 63 sectors per track. Otherwise // we're looking for a geometry that's valid for both BIOS // and ATA. if (totalSectors > 65535 * 16 * 63) { sectorsPerTrack = 255; headsPerCylinder = 16; } else { sectorsPerTrack = 17; int cylindersTimesHeads = totalSectors / sectorsPerTrack; headsPerCylinder = (cylindersTimesHeads + 1023) / 1024; if (headsPerCylinder < 4) { headsPerCylinder = 4; } // If we need more than 1023 cylinders, or 16 heads, try more sectors per track if (cylindersTimesHeads >= (headsPerCylinder * 1024U) || headsPerCylinder > 16) { sectorsPerTrack = 31; headsPerCylinder = 16; cylindersTimesHeads = totalSectors / sectorsPerTrack; } // We need 63 sectors per track to keep the cylinder count down if (cylindersTimesHeads >= (headsPerCylinder * 1024U)) { sectorsPerTrack = 63; headsPerCylinder = 16; } } cylinders = (totalSectors / sectorsPerTrack) / headsPerCylinder; return new Geometry(cylinders, headsPerCylinder, sectorsPerTrack, sectorSize); } /// /// Converts a CHS (Cylinder,Head,Sector) address to a LBA (Logical Block Address). /// /// The CHS address to convert. /// The Logical Block Address (in sectors). public long ToLogicalBlockAddress(ChsAddress chsAddress) { return ToLogicalBlockAddress(chsAddress.Cylinder, chsAddress.Head, chsAddress.Sector); } /// /// Converts a CHS (Cylinder,Head,Sector) address to a LBA (Logical Block Address). /// /// The cylinder of the address. /// The head of the address. /// The sector of the address. /// The Logical Block Address (in sectors). public long ToLogicalBlockAddress(int cylinder, int head, int sector) { if (cylinder < 0) { throw new ArgumentOutOfRangeException("cylinder", cylinder, "cylinder number is negative"); } if (head >= _headsPerCylinder) { throw new ArgumentOutOfRangeException("head", head, "head number is larger than disk geometry"); } if (head < 0) { throw new ArgumentOutOfRangeException("head", head, "head number is negative"); } if (sector > _sectorsPerTrack) { throw new ArgumentOutOfRangeException("sector", sector, "sector number is larger than disk geometry"); } if (sector < 1) { throw new ArgumentOutOfRangeException("sector", sector, "sector number is less than one (sectors are 1-based)"); } return (((cylinder * (long)_headsPerCylinder) + head) * _sectorsPerTrack) + sector - 1; } /// /// Converts a LBA (Logical Block Address) to a CHS (Cylinder, Head, Sector) address. /// /// The logical block address (in sectors). /// The address in CHS form. public ChsAddress ToChsAddress(long logicalBlockAddress) { if (logicalBlockAddress < 0) { throw new ArgumentOutOfRangeException("logicalBlockAddress", logicalBlockAddress, "Logical Block Address is negative"); } int cylinder = (int)(logicalBlockAddress / (_headsPerCylinder * _sectorsPerTrack)); int temp = (int)(logicalBlockAddress % (_headsPerCylinder * _sectorsPerTrack)); int head = temp / _sectorsPerTrack; int sector = (temp % _sectorsPerTrack) + 1; return new ChsAddress(cylinder, head, sector); } /// /// Translates an IDE (aka Physical) geometry to a BIOS (aka Logical) geometry. /// /// The translation to perform. /// The translated disk geometry. public Geometry TranslateToBios(GeometryTranslation translation) { return TranslateToBios(0, translation); } /// /// Translates an IDE (aka Physical) geometry to a BIOS (aka Logical) geometry. /// /// The capacity of the disk, required if the geometry is an approximation on the actual disk size. /// The translation to perform. /// The translated disk geometry. public Geometry TranslateToBios(long capacity, GeometryTranslation translation) { if (capacity <= 0) { capacity = TotalSectorsLong * 512L; } switch (translation) { case GeometryTranslation.None: return this; case GeometryTranslation.Auto: if (IsBiosSafe) { return this; } else { return Geometry.LbaAssistedBiosGeometry(capacity); } case GeometryTranslation.Lba: return Geometry.LbaAssistedBiosGeometry(capacity); case GeometryTranslation.Large: return Geometry.LargeBiosGeometry(this); default: throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Translation mode '{0}' not yet implemented", translation), "translation"); } } /// /// Determines if this object is equivalent to another. /// /// The object to test against. /// true if the is equivalent, else false. public override bool Equals(object obj) { if (obj == null || obj.GetType() != GetType()) { return false; } Geometry other = (Geometry)obj; return _cylinders == other._cylinders && _headsPerCylinder == other._headsPerCylinder && _sectorsPerTrack == other._sectorsPerTrack && _bytesPerSector == other._bytesPerSector; } /// /// Calculates the hash code for this object. /// /// The hash code. public override int GetHashCode() { return _cylinders.GetHashCode() ^ _headsPerCylinder.GetHashCode() ^ _sectorsPerTrack.GetHashCode() ^ _bytesPerSector.GetHashCode(); } /// /// Gets a string representation of this object, in the form (C/H/S). /// /// The string representation. public override string ToString() { if (_bytesPerSector == 512) { return "(" + _cylinders + "/" + _headsPerCylinder + "/" + _sectorsPerTrack + ")"; } else { return "(" + _cylinders + "/" + _headsPerCylinder + "/" + _sectorsPerTrack + ":" + _bytesPerSector + ")"; } } } }