May 23

I’ve been messing around with disk I/O in C# lately, attempting to read/modify the boot sectors and such.  I’ve used the lower-level functions for accessing disks like files, from the Win32 API.  Most of the signatures were adapted from the pinvoke.net website as well as some examples from MSDN.

What I’ve done is made one file/class to hold all the unsafe/unmanaged code and another to wrap those functions up nicely in a .NET-style class named DiskStream.  The DiskStream class inherits from the Stream object and so exposes an interface similar to a FileStream.  You can open up a DiskStream and read, write, and seek it just like a regular file.

There are a few gotcha’s associated with performing disk access like this:

  • You need to access the disk in multiples of the sector size (ie. 512 bytes).
  • I don’t believe this will work at all in any Windows below Win2K.
  • Currently has odd behaviour on disks with mounted volumes, if there’s a mounted volume, it appears only the MBR can be written.  I’m going to fix this in a future version so that the volumes get locked and/or dismounted before hand.

So here’s the code for accessing the Win32 API from .NET. I think this is pretty useful, since I had a hard time tracking down this stuff and making it work. Here’s the first class, which I call DeviceIO:

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.Win32.SafeHandles;
namespace DiskLib
{
    /// 
    /// P/Invoke wrappers around Win32 functions and constants.
    /// 
    internal class DeviceIO
    {
        #region Constants used in unmanaged functions
        public const uint FILE_SHARE_READ = 0x00000001;
        public const uint FILE_SHARE_WRITE = 0x00000002;
        public const uint FILE_SHARE_DELETE = 0x00000004;
        public const uint OPEN_EXISTING = 3;
        public const uint GENERIC_READ = (0x80000000);
        public const uint GENERIC_WRITE = (0x40000000);
        public const uint FILE_FLAG_NO_BUFFERING = 0x20000000;
        public const uint FILE_FLAG_WRITE_THROUGH = 0x80000000;
        public const uint FILE_READ_ATTRIBUTES = (0x0080);
        public const uint FILE_WRITE_ATTRIBUTES = 0x0100;
        public const uint ERROR_INSUFFICIENT_BUFFER = 122;
        #endregion
 
        #region Unamanged function declarations
        [DllImport("kernel32.dll", SetLastError = true)]
        public static unsafe extern SafeFileHandle CreateFile(
            string FileName,
            uint DesiredAccess,
            uint ShareMode,
            IntPtr SecurityAttributes,
            uint CreationDisposition,
            uint FlagsAndAttributes,
            IntPtr hTemplateFile);
 
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool CloseHandle(SafeFileHandle hHandle);
 
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool DeviceIoControl(
            SafeFileHandle hDevice,
            uint dwIoControlCode,
            IntPtr lpInBuffer,
            uint nInBufferSize,
            [Out] IntPtr lpOutBuffer,
            uint nOutBufferSize,
            ref uint lpBytesReturned,
            IntPtr lpOverlapped);
 
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern unsafe bool WriteFile(
            SafeFileHandle hFile,
            void* pBuffer,
            int NumberOfBytesToWrite,
            int* pNumberOfBytesWritten,
            int Overlapped);
 
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern unsafe bool ReadFile(
            SafeFileHandle hFile,
            void* pBuffer,
            int NumberOfBytesToRead,
            int* pNumberOfBytesRead,
            int Overlapped);
 
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool SetFilePointerEx(
            SafeFileHandle hFile,
            long liDistanceToMove,
            out long lpNewFilePointer,
            uint dwMoveMethod);
 
        [DllImport("kernel32.dll")]
        public static extern bool FlushFileBuffers(
            SafeFileHandle hFile);
        #endregion
 
    }
 
}

With that out of the way, I wrote a Stream class to make accessing the disk more “,NET-ish”. Here’s the code for the stream class:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
 
namespace DiskLib
{
 
    public class DiskStream : Stream
    {
        public const int DEFAULT_SECTOR_SIZE = 512;
        private const int BUFFER_SIZE = 4096;
        private string diskID;
        private DiskInfo diskInfo;
        private FileAccess desiredAccess;
        private SafeFileHandle fileHandle;
 
        public DiskInfo DiskInfo
        {
            get { return this.diskInfo; }
        }
 
        public int SectorSize
        {
            get { return (int)this.diskInfo.BytesPerSector; }
        }
 
        public DiskStream(string diskID, FileAccess desiredAccess)
        {
            this.diskID = diskID;
            this.diskInfo = new DiskInfo(diskID);
            this.desiredAccess = desiredAccess;
            // if desiredAccess is Write or Read/Write
            //   find volumes on this disk
            //   lock the volumes using FSCTL_LOCK_VOLUME
            //     unlock the volumes on Close() or in destructor
            this.fileHandle = this.openFile(diskID, desiredAccess);
        }
 
        private SafeFileHandle openFile(string id, FileAccess desiredAccess)
        {
            uint access;
            switch (desiredAccess)
            {
                case FileAccess.Read:
                    access = DeviceIO.GENERIC_READ;
                    break;
                case FileAccess.Write:
                    access = DeviceIO.GENERIC_WRITE;
                    break;
                case FileAccess.ReadWrite:
                    access = DeviceIO.GENERIC_READ | DeviceIO.GENERIC_WRITE;
                    break;
                default:
                    access = DeviceIO.GENERIC_READ;
                    break;
            }
 
            SafeFileHandle ptr = DeviceIO.CreateFile(
                id,
                access,
                DeviceIO.FILE_SHARE_READ,
                IntPtr.Zero,
                DeviceIO.OPEN_EXISTING,
                DeviceIO.FILE_FLAG_NO_BUFFERING | DeviceIO.FILE_FLAG_WRITE_THROUGH,
                IntPtr.Zero);
 
            if (ptr.IsInvalid)
            {
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
            }
 
            return ptr;
        }
 
        public override bool CanRead
        {
            get
            {
                return (this.desiredAccess == FileAccess.Read || this.desiredAccess == FileAccess.ReadWrite) ? true : false;
            }
        }
 
        public override bool CanWrite
        {
            get
            {
                return (this.desiredAccess == FileAccess.Write || this.desiredAccess == FileAccess.ReadWrite) ? true : false;
            }
        }
 
        public override bool CanSeek
        {
            get
            {
                return true;
            }
        }
 
        public override long Length
        {
            get { return this.diskInfo.Size; }
        }
 
        public override long Position
        {
            get
            {
                long n = 0;
                if (!DeviceIO.SetFilePointerEx(this.fileHandle, 0, out n, (uint)SeekOrigin.Current))
                    Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
                return n;
            }
            set
            {
                if (value > (this.Length - 1))
                    throw new EndOfStreamException("Cannot set position beyond the end of the disk.");
                long n = 0;
                if (!DeviceIO.SetFilePointerEx(this.fileHandle, value, out n, (uint)SeekOrigin.Begin))
                    Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
            }
        }
 
        public override void Flush()
        {
            // not required, since FILE_FLAG_WRITE_THROUGH and FILE_FLAG_NO_BUFFERING are used
            //if (!Unmanaged.FlushFileBuffers(this.fileHandle))
            //    Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
        }
 
        public override void Close()
        {
            DeviceIO.CloseHandle(this.fileHandle);
            base.Close();
        }
 
        public override void SetLength(long value)
        {
            throw new NotSupportedException("Setting the length is not supported with DiskStream objects.");
        }
 
        public override unsafe int Read(byte[] buffer, int offset, int count)
        {
            int n = 0;
            fixed (byte* p = buffer)
            {
                if (!DeviceIO.ReadFile(this.fileHandle, p + offset, count, &n, 0))
                    Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
            }
            return n;
        }
 
        public override unsafe void Write(byte[] buffer, int offset, int count)
        {
            int n = 0;
            fixed (byte* p = buffer)
            {
                if (!DeviceIO.WriteFile(this.fileHandle, p + offset, count, &n, 0))
                    Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
            }
        }
 
        public override long Seek(long offset, SeekOrigin origin)
        {
            long n = 0;
            if (!DeviceIO.SetFilePointerEx(this.fileHandle, offset, out n, (uint)origin))
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
            return n;
        }
 
        public int ReadSector(DiskSector sector)
        {
            return this.Read(sector.Data, 0, sector.SectorSize);
        }
 
        public void WriteSector(DiskSector sector)
        {
            this.Write(sector.Data, 0, sector.SectorSize);
        }
 
        public void SeekSector(DiskSector sector)
        {
            this.Seek(sector.Offset, SeekOrigin.Begin);
        }
    }
 
    public struct DiskSector
    {
        public const int DEFAULT_SECTOR_SIZE = 512;
        private long offset;
        private byte[] data;
        private int sectorSize;
        public int SectorSize
        {
            get { return this.sectorSize; }
            set
            {
                if ((value % 2) != 0)
                    throw new ArgumentException("Sector size must be a multiple of 2.");
                this.sectorSize = value;
            }
        }
 
        public long Offset
        {
            get { return this.offset; }
            set
            {
                if ((value % this.SectorSize) != 0)
                    throw new ArgumentException("Sector offset must be a multiple of SectorSize.");
                this.offset = value;
            }
        }
 
        public byte[] Data
        {
            get { return this.data; }
            set
            {
                if (value.Length != this.SectorSize)
                    throw new ArgumentException("Data length must be the same as SectorSize.");
                this.data = value;
            }
        }
 
        public DiskSector(byte[] sectorData, long sectorOffset, int sectorSize)
        {
            if ((sectorSize % 2) != 0)
                throw new ArgumentException("Sector size must be a multiple of 2.");
            this.sectorSize = sectorSize;
            if (sectorData.Length != sectorSize)
                throw new ArgumentException("Data length must be the same as SectorSize.");
            this.data = sectorData;
            if ((sectorOffset % sectorSize) != 0)
                throw new ArgumentException("Sector offset must be a multiple of SectorSize.");
            this.offset = sectorOffset;
        }
 
        public DiskSector(byte[] sectorData, long sectorOffset)
        {
            int sectorSize = DEFAULT_SECTOR_SIZE;
            this.sectorSize = sectorSize;
            if (sectorData.Length != sectorSize)
                throw new ArgumentException("Data length must be the same as SectorSize.");
            this.data = sectorData;
            if ((sectorOffset % sectorSize) != 0)
                throw new ArgumentException("Sector offset must be a multiple of SectorSize.");
            this.offset = sectorOffset;
        }
 
        public DiskSector(long sectorOffset)
        {
            int sectorSize = DEFAULT_SECTOR_SIZE;
            this.sectorSize = sectorSize;
            this.data = new byte[sectorSize];
            if ((sectorOffset % sectorSize) != 0)
                throw new ArgumentException("Sector offset must be a multiple of SectorSize.");
            this.offset = sectorOffset;
        }
    }
 
}

You might’ve noticed the DiskInfo class, I’m not going to post the code here, but I’ll provide a download link. It’s currently just a wrapper around WMI/Win32_DiskDrive, although I’m planning on removing the WMI code and replacing it with more platform invoke calls. I’ve also added a DiskSector struct to (attempt to) make accessing the disk using sectors easier. The DiskSector struct can be used with the ReadDiskSector(), WriteDiskSector() and SeekDiskSector() methods of the DiskStream class.

And finally a WARNING to anyone intending to use this code: messing around with disks like this is EXTREMELY dangerous and you can hose your whole system very easily. I HIGHLY recommend you play with this code within a Virtual Machine (VirtualBox, Qemu, etc) or on a separate system that you don’t mind losing.

It’s also important to note that this is very early code, and it has not at all been thoroughly tested in any way. I’ve used it to toggle some bits in the MBR, read the partition table, and to wipe a disk clean, and that’s the extent of my testing. Proceed with caution and at your own risk.

I’ll update this post as I improve the code here.

Here’s a bunch of source files which could be useful in Zip format.

  • Share/Bookmark
Sep 11

I’ve had this super simple “Web Service” up on my other website for some time now, and I’ve used it fairly often through the browser and in scripts. I decided to make an even simpler one on CodeBrainz.ca for others and myself to use. The old service returned a single-node XML document, which is basically pointless and just adds a tiny bit of complexity on the client who is using it. The new address of the service is:

http://showip.codebrainz.ca

The actual code for the new service is a whopping 1 line of code:

<?php
 
echo $_SERVER['REMOTE_ADDR'];
 
?>


I’ve whipped up a couple ultra simple apps and a pointlessly simple library to save writing a few lines of code. The bash script is for for *nix-style systems while the .NET binaries are for Windows/.NET/Mono.

Here are links to each file:

I hope someone out there finds this simple service as useful as I have.

  • Share/Bookmark
Sep 08

I posted this on CodeProject a while back…

“The library is meant to simplify getting the current weather conditions (as well as a 2 day forecast) into your .NET programs. I have created various objects to wrap the functionality provided by the Yahoo! Weather RSS Feed. I have enumerated various variables in an attempt to make the library simpler to use…”

View the article on The Code Project.

  • Share/Bookmark
Sep 03

As part of a project I’m working on to create a GPS/mapping program and to learn about GIS in general, I’ve started working on class library to represent geographical coordinates (latitude and longitude) and to convert between the various formats/representations of those coordinates.  It also includes the beginnings of a Parse() method, which is not tested and likely doesn’t work yet, which will take in virtually any common coordinate string and convert it to a Coordinate object.

It probably took longer to document the class than it did to write the code, but it may prove useful to others, so it was worth it.  When I get a chance, I’ll attach a binary/dll for download along with the accompanying XML documentation.


If anyone uses this, make sure to let me know if you find any problems so I can update my code.

Here is the current C# code file:
Coordinate Class File

Will re-upload the file shortly.

  • Share/Bookmark
Sep 01

It doesn’t get much easier than this:

using System.Net;
using System.Net.NetworkInformation;
...
public static bool Ping(string ipAddress)
{
    System.Net.NetworkInformation.Ping ping =
        new System.Net.NetworkInformation.Ping();
    IPAddress address = IPAddress.Parse(ipAddress);
    PingReply reply = ping.Send(address, 30);
    return (reply.Status == IPStatus.Success) ? true : false;
}
  • Share/Bookmark

1,134 spam comments
blocked by
Akismet