Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
229 views
in Technique[技术] by (71.8m points)

c# - Physical disk size not correct (IoCtlDiskGetDriveGeometry)

I use the code below to get the physical disk size, but the size returned is not correct. I've checked the size with other tools.

The code below reports

Total disk space: 8.249.955.840 bytes

and it should be

Total disk space: 8.254.390.272 bytes

How can I retrieve the actual/correct physical disk size? Tested on USB drives and normal hard drives. The code is long, here separate it in parts to show.

The structure:

[StructLayout(LayoutKind.Sequential)]
internal struct DiskGeometry {
    public long Cylinders;
    public int MediaType;
    public int TracksPerCylinder;
    public int SectorsPerTrack;
    public int BytesPerSector;
}

Native methods:

internal static class NativeMethods {
    [DllImport("Kernel32.dll", SetLastError=true, CharSet=CharSet.Auto)]
    public static extern SafeFileHandle CreateFile(
        string fileName,
        uint fileAccess,
        uint fileShare,
        IntPtr securityAttributes,
        uint creationDisposition,
        uint flags,
        IntPtr template
        );

    [DllImport("Kernel32.dll", SetLastError=false, CharSet=CharSet.Auto)]
    public static extern int DeviceIoControl(
        SafeFileHandle device,
        uint controlCode,
        IntPtr inBuffer,
        uint inBufferSize,
        IntPtr outBuffer,
        uint outBufferSize,
        ref uint bytesReturned,
        IntPtr overlapped
        );

    internal const uint FileAccessGenericRead=0x80000000;
    internal const uint FileShareWrite=0x2;
    internal const uint FileShareRead=0x1;
    internal const uint CreationDispositionOpenExisting=0x3;
    internal const uint IoCtlDiskGetDriveGeometry=0x70000;
}

Main entry:

internal const uint IoCtlDiskGetDriveGeometry=0x70000;

public static void Main() {
    SafeFileHandle diskHandle=
        NativeMethods.CreateFile(
            @"\.PhysicalDrive0",
            NativeMethods.FileAccessGenericRead,
            NativeMethods.FileShareWrite|NativeMethods.FileShareRead,
            IntPtr.Zero,
            NativeMethods.CreationDispositionOpenExisting,
            0,
            IntPtr.Zero
            );

    if(diskHandle.IsInvalid) {
        Console.WriteLine("CreateFile failed with error: {0}", Marshal.GetLastWin32Error());
        return;
    }

    int geometrySize=Marshal.SizeOf(typeof(DiskGeometry));
    Console.WriteLine("geometry size = {0}", geometrySize);

    IntPtr geometryBlob=Marshal.AllocHGlobal(geometrySize);
    uint numBytesRead=0;

    if(
        0==NativeMethods.DeviceIoControl(
            diskHandle,
            NativeMethods.IoCtlDiskGetDriveGeometry,
            IntPtr.Zero,
            0,
            geometryBlob,
            (uint)geometrySize,
            ref numBytesRead,
            IntPtr.Zero
            )
        ) {
        Console.WriteLine(
            "DeviceIoControl failed with error: {0}",
            Marshal.GetLastWin32Error()
            );

        return;
    }

    Console.WriteLine("Bytes read = {0}", numBytesRead);

    DiskGeometry geometry=(DiskGeometry)Marshal.PtrToStructure(geometryBlob, typeof(DiskGeometry));
    Marshal.FreeHGlobal(geometryBlob);

    long bytesPerCylinder=(long)geometry.TracksPerCylinder*(long)geometry.SectorsPerTrack*(long)geometry.BytesPerSector;
    long totalSize=geometry.Cylinders*bytesPerCylinder;

    Console.WriteLine("Media Type:           {0}", geometry.MediaType);
    Console.WriteLine("Cylinders:            {0}", geometry.Cylinders);
    Console.WriteLine("Tracks per Cylinder:  {0}", geometry.TracksPerCylinder);
    Console.WriteLine("Sectors per Track:    {0}", geometry.SectorsPerTrack);
    Console.WriteLine("Bytes per Sector:     {0}", geometry.BytesPerSector);
    Console.WriteLine("Bytes per Cylinder:   {0}", bytesPerCylinder);
    Console.WriteLine("Total disk space:     {0}", totalSize);
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

After did some study of DeviceIocontrol, and most of time I spend on designing. Here I post the code in two parts, separated with namespace and partial classes for the clarity, you can merge them but cannot use them individually.

namespace DiskManagement {
    using Microsoft.Win32.SafeHandles;

    using LPSECURITY_ATTRIBUTES=IntPtr;
    using LPOVERLAPPED=IntPtr;
    using LPVOID=IntPtr;
    using HANDLE=IntPtr;

    using LARGE_INTEGER=Int64;
    using DWORD=UInt32;
    using LPCTSTR=String;

    public static partial class IoCtl /* methods */ {
        [DllImport("kernel32.dll", SetLastError=true)]
        static extern SafeFileHandle CreateFile(
            LPCTSTR lpFileName,
            DWORD dwDesiredAccess,
            DWORD dwShareMode,
            LPSECURITY_ATTRIBUTES lpSecurityAttributes,
            DWORD dwCreationDisposition,
            DWORD dwFlagsAndAttributes,
            HANDLE hTemplateFile
            );

        [DllImport("kernel32.dll", SetLastError=true)]
        static extern DWORD DeviceIoControl(
            SafeFileHandle hDevice,
            DWORD dwIoControlCode,
            LPVOID lpInBuffer,
            DWORD nInBufferSize,
            LPVOID lpOutBuffer,
            int nOutBufferSize,
            ref DWORD lpBytesReturned,
            LPOVERLAPPED lpOverlapped
            );

        static DWORD CTL_CODE(DWORD DeviceType, DWORD Function, DWORD Method, DWORD Access) {
            return (((DeviceType)<<16)|((Access)<<14)|((Function)<<2)|(Method));
        }

        public static void Execute<T>(
            ref T x,
            DWORD dwIoControlCode,
            LPCTSTR lpFileName,
            DWORD dwDesiredAccess=GENERIC_READ,
            DWORD dwShareMode=FILE_SHARE_WRITE|FILE_SHARE_READ,
            LPSECURITY_ATTRIBUTES lpSecurityAttributes=default(LPSECURITY_ATTRIBUTES),
            DWORD dwCreationDisposition=OPEN_EXISTING,
            DWORD dwFlagsAndAttributes=0,
            HANDLE hTemplateFile=default(IntPtr)
            ) {
            using(
                var hDevice=
                    CreateFile(
                        lpFileName,
                        dwDesiredAccess, dwShareMode,
                        lpSecurityAttributes,
                        dwCreationDisposition, dwFlagsAndAttributes,
                        hTemplateFile
                        )
                ) {
                if(null==hDevice||hDevice.IsInvalid)
                    throw new Win32Exception(Marshal.GetLastWin32Error());

                var nOutBufferSize=Marshal.SizeOf(typeof(T));
                var lpOutBuffer=Marshal.AllocHGlobal(nOutBufferSize);
                var lpBytesReturned=default(DWORD);
                var NULL=IntPtr.Zero;

                var result=
                    DeviceIoControl(
                        hDevice, dwIoControlCode,
                        NULL, 0,
                        lpOutBuffer, nOutBufferSize,
                        ref lpBytesReturned, NULL
                        );

                if(0==result)
                    throw new Win32Exception(Marshal.GetLastWin32Error());

                x=(T)Marshal.PtrToStructure(lpOutBuffer, typeof(T));
                Marshal.FreeHGlobal(lpOutBuffer);
            }
        }
    }

    public enum MEDIA_TYPE: int {
        Unknown=0,
        F5_1Pt2_512=1,
        F3_1Pt44_512=2,
        F3_2Pt88_512=3,
        F3_20Pt8_512=4,
        F3_720_512=5,
        F5_360_512=6,
        F5_320_512=7,
        F5_320_1024=8,
        F5_180_512=9,
        F5_160_512=10,
        RemovableMedia=11,
        FixedMedia=12,
        F3_120M_512=13,
        F3_640_512=14,
        F5_640_512=15,
        F5_720_512=16,
        F3_1Pt2_512=17,
        F3_1Pt23_1024=18,
        F5_1Pt23_1024=19,
        F3_128Mb_512=20,
        F3_230Mb_512=21,
        F8_256_128=22,
        F3_200Mb_512=23,
        F3_240M_512=24,
        F3_32M_512=25
    }

    partial class DiskGeometry /* structures */ {
        [StructLayout(LayoutKind.Sequential)]
        struct DISK_GEOMETRY {
            internal LARGE_INTEGER Cylinders;
            internal MEDIA_TYPE MediaType;
            internal DWORD TracksPerCylinder;
            internal DWORD SectorsPerTrack;
            internal DWORD BytesPerSector;
        }

        [StructLayout(LayoutKind.Sequential)]
        struct DISK_GEOMETRY_EX {
            internal DISK_GEOMETRY Geometry;
            internal LARGE_INTEGER DiskSize;

            [MarshalAs(UnmanagedType.ByValArray, SizeConst=1)]
            internal byte[] Data;
        }
    }

    partial class DiskGeometry /* properties and fields */ {
        public MEDIA_TYPE MediaType {
            get {
                return m_Geometry.MediaType;
            }
        }

        public String MediaTypeName {
            get {
                return Enum.GetName(typeof(MEDIA_TYPE), this.MediaType);
            }
        }

        public override long Cylinder {
            get {
                return m_Geometry.Cylinders;
            }
        }

        public override uint Head {
            get {
                return m_Geometry.TracksPerCylinder;
            }
        }

        public override uint Sector {
            get {
                return m_Geometry.SectorsPerTrack;
            }
        }

        public DWORD BytesPerSector {
            get {
                return m_Geometry.BytesPerSector;
            }
        }

        public long DiskSize {
            get {
                return m_DiskSize;
            }
        }

        public long MaximumLinearAddress {
            get {
                return m_MaximumLinearAddress;
            }
        }

        public CubicAddress MaximumCubicAddress {
            get {
                return m_MaximumCubicAddress;
            }
        }

        public DWORD BytesPerCylinder {
            get {
                return m_BytesPerCylinder;
            }
        }

        CubicAddress m_MaximumCubicAddress;
        long m_MaximumLinearAddress;
        DWORD m_BytesPerCylinder;
        LARGE_INTEGER m_DiskSize;
        DISK_GEOMETRY m_Geometry;
    }
}

First off, I use the using alias directive to make native calls of code more like in C/C++. The point of the first part, is IoCtl.Execute method. It is a generic method and the type is according to the first argument passed. It hides the complexity of marshalling structures and pointers with P/Invoke methods. The second parameter is the desired control code which will pass to DeviceIoControl. From the third to the last parameter are exact the same as CreateFile, and all have default value, they are optional.

Following is the next part of code, and might have more things to mention.

namespace DiskManagement {
    using Microsoft.Win32.SafeHandles;

    using LPSECURITY_ATTRIBUTES=IntPtr;
    using LPOVERLAPPED=IntPtr;
    using LPVOID=IntPtr;
    using HANDLE=IntPtr;

    using LARGE_INTEGER=Int64;
    using DWORD=UInt32;
    using LPCTSTR=String;

    partial class IoCtl /* constants */ {
        public const DWORD
            DISK_BASE=0x00000007,
            METHOD_BUFFERED=0,
            FILE_ANY_ACCESS=0;

        public const DWORD
            GENERIC_READ=0x80000000,
            FILE_SHARE_WRITE=0x2,
            FILE_SHARE_READ=0x1,
            OPEN_EXISTING=0x3;

        public static readonly DWORD DISK_GET_DRIVE_GEOMETRY_EX=
            IoCtl.CTL_CODE(DISK_BASE, 0x0028, METHOD_BUFFERED, FILE_ANY_ACCESS);

        public static readonly DWORD DISK_GET_DRIVE_GEOMETRY=
            IoCtl.CTL_CODE(DISK_BASE, 0, METHOD_BUFFERED, FILE_ANY_ACCESS);
    }

    public partial class CubicAddress {
        public static CubicAddress Transform(long linearAddress, CubicAddress geometry) {
            var cubicAddress=new CubicAddress();
            var sectorsPerCylinder=geometry.Sector*geometry.Head;
            long remainder;
            cubicAddress.Cylinder=Math.DivRem(linearAddress, sectorsPerCylinder, out remainder);
            cubicAddress.Head=(uint)Math.DivRem(remainder, geometry.Sector, out remainder);
            cubicAddress.Sector=1+(uint)remainder;
            return cubicAddress;
        }

        public virtual long Cylinder {
            get;
            set;
        }

        public virtual uint Head {
            get;
            set;
        }

        public virtual uint Sector {
            get;
            set;
        }
    }

    public partial class DiskGeometry: CubicAddress {
        internal static void ThrowIfDiskSizeOutOfIntegrity(long remainder) {
            if(0!=remainder) {
                var message="DiskSize is not an integral multiple of a sector size";
                throw new ArithmeticException(message);
            }
        }

        public static DiskGeometry FromDevice(String deviceName) {
            return new DiskGeometry(deviceName);
        }

        DiskGeometry(String deviceName) {
            var x=new DISK_GEOMETRY_EX();
            IoCtl.Execute(ref x, IoCtl.DISK_GET_DRIVE_GEOMETRY_EX, deviceName);
            m_DiskSize=x.DiskSize;
            m_Geometry=x.Geometry;

            long remainder;
            m_MaximumLinearAddress=Math.DivRem(DiskSize, BytesPerSector, out remainder)-1;
            ThrowIfDiskSizeOutOfIntegrity(remainder);

            m_BytesPerCylinder=BytesPerSector*Sector*Head;
            m_MaximumCubicAddress=DiskGeometry.Transform(m_MaximumLinearAddress, this);
        }
    }
}

The IoCtl.CTL_CODE is originally a macro in C/C++ code, but c# doesn't have macros, so that I change the declaration like DISK_GET_DRIVE_GEOMETRY_EX as static readonly values, treated as runtime constants. Prefix of some constants like IOCTL_ are removed, because there're class names to qualify them. The biggest point of this part would be the class CubicAddress, it's the base of newly defined class DiskGeometry. You might wonder of why or even more of wondering.

The class CubicAddress is, in fact, a simple class use to store CHS address of phisical disks and provide a method converting the address from LBA format, which I named Transform. Although I never heard somebody names the CHS as something cubic, but I think the terms like geometry/volumes are have the same usage in mathematics and arround physical disks.

CHS is likely, (x ,y, z), (R, G, B) or any other things which you can model them in a cubic manner. They might have a coordinate for addressing, which may also used to describe the geometry, like a vector. Thus, the class CubicAddress has two usages:

  • presents an address of sector
  • describes the geometry

CHS/LBA conversions are linear transformation/combination, and I wrote only Transform which is for LBA to CHS. The parameter geometry of Transform is the geometry referenced for the transformation, it's required because of a linear address can be transformed to a different coordinate with a different geometry.

About naming, representing of the terms like SectorsPerTrack</co


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

1.4m articles

1.4m replys

5 comments

57.0k users

...