Skip to content

Conversation

DamirAinullin
Copy link

@DamirAinullin DamirAinullin commented May 7, 2018

Fix PARTITION_INFORMATION_GPT structure according to url. Works better for my case, for example I get correct partitionType Guid = ebd0a0a2-b9e5-4433-87c0-68b6b72699c7 instead of wrong eb0101a2-b9e5-4433-87c0-68b6b72699c7.

@LordMike
Copy link
Owner

LordMike commented May 7, 2018

I find it very odd that the description of placement of the guids should have an effect. I've tried out the following:

  • Create a structure with data (the ids you come with)
  • Marshal it to unmanaged data
  • Marshal it back to C# structures - both as the current and the new format
  • Verify the guids

I cannot reproduce a (simple) case where the guid would be read differently.

Could you try to fetch out the data you're seeing? (Use Marshal.Copy to copy it to a C# byte[], and then hex/base64 encode it..).

Test code.

using System;
using System.Runtime.InteropServices;

namespace ConsoleApp17
{
    class Program
    {
        static void Main(string[] args)
        {
            PARTITION_INFORMATION_GPT orig = new PARTITION_INFORMATION_GPT
            {
                Attributes = 500,
                Name = "Test",
                PartitionId = new Guid("ebd0a0a2-b9e5-4433-87c0-68b6b72699c7"),
                PartitionType = new Guid("ebd0a0a2-b9e5-4433-87c0-68b6b72699c7")
            };

            var size = Marshal.SizeOf<PARTITION_INFORMATION_GPT>();
            var ptr = Marshal.AllocHGlobal(size);

            Marshal.StructureToPtr(orig,ptr,false);

            var asOld = Marshal.PtrToStructure<PARTITION_INFORMATION_GPT>(ptr);
            var asNew = Marshal.PtrToStructure<PARTITION_INFORMATION_GPT_NEW>(ptr);

            Console.WriteLine("Old guid: " + asOld.PartitionType);
            Console.WriteLine("New guid: " + asNew.PartitionType);
        }
    }

    [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)]
    public struct PARTITION_INFORMATION_GPT
    {
        [FieldOffset(0)]
        public Guid PartitionType;
        [FieldOffset(16)]
        public Guid PartitionId;
        [FieldOffset(32)]
        [MarshalAs(UnmanagedType.U8)]
        public ulong Attributes;
        [FieldOffset(40)]
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 36)]
        public string Name;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct PARTITION_INFORMATION_GPT_NEW
    {
        public Guid PartitionType;
        public Guid PartitionId;
        [MarshalAs(UnmanagedType.U8)]
        public ulong Attributes;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 36)]
        public string Name;
    }
}

@DamirAinullin
Copy link
Author

DamirAinullin commented May 7, 2018

I tried to test your code. It works well without any problem. My case is the next: I just use your example, (ExampleDiskIO method) but with string driveName = @"\\.\C:";. And then I see such problem. If I use PARTITION_INFORMATION_GPT_NEW I see problem with Attributes (strange value 4914476011590058402).

@LordMike
Copy link
Owner

LordMike commented May 7, 2018

Well, to be fair, I didn't include the Enum value in my short test.

@Vengariel
Copy link

Vengariel commented Jun 3, 2019

Don't know if it's relevant anymore, but I've pinpointed the issue @DamirAinullin originally stated.

Making the struct Sequential didn't work for me, because I started getting runtime errors about misaligned memory positions.

It starts with the PARTITION_INFORMATION_UNION file, where the same offset is assigned to PARTITION_INFORMATION_GPT and PARTITION_INFORMATION_MBR (hence, the union).

After some research, structures that are used in a union, have to be decorated with [StructLayout(LayoutKind.Explicit)] and in consequence, each field have it's FieldOffset to properly allow the compiler to handle them.

Once setting the above, you can isolate the error to the members BootIndicator & RecognizedPartition of the PARTITION_INFORMATION_MBR. Bool type is not blittable.
Depending on the field offset value you set, it's possible to 'move' the failing chars over the GUID gpt partition GUID. And its consistent, it affects the GUID according to the bytes overlapping between both structs (the first 16 bytes in PARTITION_INFORMATION_GPT are for GUID, so that's the favorite victim).

From experimenting, GPT struct seems to be allocated first, and MBR afterwards. My guessing is that since bool type is somewhat strange for marshaling (look into this post https://stackoverflow.com/a/44884332), and MBR has 2 of them, they are incorrectly overwritting the bytes shared by both structs, in those particular positions.

You can swap the position of both structs in PARTITION_INFORMATION_UNION and watch the opposite: GUIDs for gpt type are correct now, and boolean fields on the MBR struct make no sense.

Making both bool fields, of byte types, seems to solve the GUID issue, since byte is blittable, but I'm not able to test with a MBR partition to make sure it doesn't break all hell loose.

@jcurl
Copy link

jcurl commented Sep 25, 2020

In regarding to the comment by @Vengariel, I can say that I am working on a similar project, using IOCTL_DISK_GET_PARTITION_INFO_EX, which also uses the union of PARTITION_INFORMATION_GPT and PARTITION_INFORMATION_MBR. In my testing, I also saw for my boot partition the weird value eb0101a2-89e5-4433-87c0-68b6b72699c7 instead of the expected value .ebd0a0a2-89e5-4433-87c0-68b6b72699c7 which diskpart correctly reported.

By changing the [MarshalAs(UnmanagedType.U1)] bool BootIndicator to simply byte BootIndicator, the problem was fixed, precisely as you've observed, and your answer on blittlable types seem very reasonable.

I see this is still in the Open state, I don't use this project in any way (I only found it by googling the GUID), but contained enough information to solve my problem, so that it might be useful for you also in deciding what to do here. Otherwise, there's really not very much information that I could find either.

        // Must be set to explicit, else it won't overlap with PARTITION_INFORMATION_MBR.
        //  See these resources:
        //  - https://stackoverflow.com/questions/14128093/marshaling-error-in-x64
        //  - https://stackoverflow.com/questions/35786764/creating-multiple-partitions-on-usb-using-c-sharp/35792276
        [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Auto)]
        public struct PARTITION_INFORMATION_GPT
        {
            [FieldOffset(0)]
            public Guid PartitionType;

            [FieldOffset(16)]
            public Guid PartitionId;

            [FieldOffset(32)]
            [MarshalAs(UnmanagedType.U8)]
            public EFIPartitionAttributes Attributes;

            [FieldOffset(40)]
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 36)]
            public string Name;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct PARTITION_INFORMATION_MBR
        {
            public byte PartitionType;

            // The BootIndicator and RecognizedPartition have to be of type 'byte', as 'bool' is not blittable. Using:
            //  [MarshalAs(UnmanagedType.U1)] bool BootIndicator
            // will actually overwrite the GUID PartitionType fields for bytes 2 and 3 to be the value 01 (which, as
            // this is a union type, those two bytes in the GUID have the same location as the next two 'bool' fields).
            public byte BootIndicator;
            public byte RecognizedPartition;

            public uint HiddenSectors;
        }

        [StructLayout(LayoutKind.Explicit)]
        public struct PARTITION_INFORMATION_UNION
        {
            [FieldOffset(0)]
            public PARTITION_INFORMATION_GPT Gpt;

            [FieldOffset(0)]
            public PARTITION_INFORMATION_MBR Mbr;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct PARTITION_INFORMATION_EX
        {
            [MarshalAs(UnmanagedType.U4)]
            public PartitionStyle PartitionStyle;
            public long StartingOffset;
            public long PartitionLength;
            public int PartitionNumber;
            public bool RewritePartition;
            public PARTITION_INFORMATION_UNION DriveLayoutInformaiton;
        }

So my contribution to saying thanks - you helped me solve my own problem. And I did test with MBR, it appears to work (of course, you'll need to do your own check to see if the value is zero or not to convert it to a bool).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants