Windows C#实现Linux dd命令

9
我正在编写一个C#.Net应用程序,运行在Windows上,需要获取可移动磁盘的图像,并将其传输到Linux Live USB上。Live USB插入目标机器并启动后,运行一个脚本,使用dd命令将其闪存到另一个驱动器上:

dd if=/path/to/file/from/csharp/program of=/dev/sdX

我遇到的问题是在Windows端创建图像。我尝试过使用dd在Linux系统上创建的文件测试我的Live Linux,这个方法很好用,但我需要能够从C#.Net应用程序中在Windows上创建这些文件。我不想依赖于cygwin或其他依赖项,因此尝试使用Win32 CreateFile函数打开物理设备。
CreateFile的第一个参数设置为“\.\F:”(如果F:是我要成像的驱动器),如下所示:
SafeFileHandle TheDevice = CreateFile(_DevicePath, (uint)FileAccess.Read, (uint)(FileShare.Write | FileShare.Read | FileShare.Delete), IntPtr.Zero, (uint)FileMode.Open, (uint)FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_SEQUENTIAL_SCAN, IntPtr.Zero);
if (TheDevice.IsInvalid) 
{ 
    throw new IOException("Unable to access drive. Win32 Error Code " + Marshal.GetLastWin32Error()); 
}
FileStream Dest = System.IO.File.Open(_SaveFile, FileMode.Create);
FileStream Src = new FileStream(TheDevice, FileAccess.Read);
Src.CopyTo(Dest);
Dest.Flush();
Src.Close();
Dest.Close();

但是,当使用Live Linux USB将输出文件dd回磁盘时,结果并不如预期(磁盘无法启动等),但从十六进制编辑器中检查输出文件,看起来在开头有一个MBR等)。

这是字节序的问题还是应该使用其他东西来复制数据到文件中,而不是使用FileStream。

或者是否有Windows dd源代码示例(C#或C ++,我已经查看了http://www.chrysocome.net/dd的Delphi,但不完全理解它或没有一个好的Delphi IDE来分析代码),以便我可以看到它的工作原理?

更新/编辑:

这里是dd输出包含的前512个字节的十六进制字符串:

33 C0 FA 8E D8 8E D0 BC 00 7C 89 E6 06 57 8E C0 FB FC BF 00 06 B9 00 01 F3 A5 EA 1F 06 
00 00 52 52 B4 41 BB AA 55 31 C9 30 F6 F9 CD 13 72 13 81 FB 55 AA 75 0D D1 E9 73 09 66 
C7 06 8D 06 B4 42 EB 15 5A B4 08 CD 13 83 E1 3F 51 0F B6 C6 40 F7 E1 52 50 66 31 C0 66 
99 E8 66 00 E8 21 01 4D 69 73 73 69 6E 67 20 6F 70 65 72 61 74 69 6E 67 20 73 79 73 74 
65 6D 2E 0D 0A 66 60 66 31 D2 BB 00 7C 66 52 66 50 06 53 6A 01 6A 10 89 E6 66 F7 36 F4 
7B C0 E4 06 88 E1 88 C5 92 F6 36 F8 7B 88 C6 08 E1 41 B8 01 02 8A 16 FA 7B CD 13 8D 64 
10 66 61 C3 E8 C4 FF BE BE 7D BF BE 07 B9 20 00 F3 A5 C3 66 60 89 E5 BB BE 07 B9 04 00 
31 C0 53 51 F6 07 80 74 03 40 89 DE 83 C3 10 E2 F3 48 74 5B 79 39 59 5B 8A 47 04 3C 0F 
74 06 24 7F 3C 05 75 22 66 8B 47 08 66 8B 56 14 66 01 D0 66 21 D2 75 03 66 89 C2 E8 AC 
FF 72 03 E8 B6 FF 66 8B 46 1C E8 A0 FF 83 C3 10 E2 CC 66 61 C3 E8 62 00 4D 75 6C 74 69 
70 6C 65 20 61 63 74 69 76 65 20 70 61 72 74 69 74 69 6F 6E 73 2E 0D 0A 66 8B 44 08 66 
03 46 1C 66 89 44 08 E8 30 FF 72 13 81 3E FE 7D 55 AA 0F 85 06 FF BC FA 7B 5A 5F 07 FA 
FF E4 E8 1E 00 4F 70 65 72 61 74 69 6E 67 20 73 79 73 74 65 6D 20 6C 6F 61 64 20 65 72 
72 6F 72 2E 0D 0A 5E AC B4 0E 8A 3E 62 04 B3 07 CD 10 3C 0A 75 F1 CD 18 F4 EB FD 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 19 16 9F 29 00 00 80 01 01 00 06 FE 3F 0E 3F 00 00 00 61 C8 03 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 AA

这是我的代码生成的结果:

EB 76 90 4D 53 44 4F 53 35 2E 30 00 02 04 04 00 02 00 02 00 00 F8 F2 00 3F 00 FF 00 3F 
00 00 00 61 C8 03 00 80 00 29 7A E8 21 04 4E 4F 20 4E 41 4D 45 20 20 20 20 46 41 54 31 
36 20 20 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 E9 05 01 B4 0E 53 33 DB CD 10 5B C3 8A 07 3C 00 74 06 E8 EE FF 43 EB F4 C3 
0D 4E 6F 20 42 50 42 3A 20 43 61 6E 27 74 20 62 6F 6F 74 20 75 73 69 6E 67 20 43 48 53 
20 66 75 6E 63 74 69 6F 6E 73 00 50 B0 2E E8 BC FF 58 33 DB 8E 06 E4 01 F6 06 DC 01 02 
75 42 F6 06 DC 01 04 75 07 80 3E E8 01 80 72 34 53 53 52 50 06 53 55 6A 10 8B F4 52 50 
8A 16 E8 01 B8 00 42 F9 CD 13 8A EC 58 5A 8D 64 10 72 14 80 FD 00 75 0F 03 C5 83 D2 00 
C3 BB 91 00 E8 78 FF F4 EB FD 83 3E 18 00 00 74 F0 52 50 8B CD F7 36 18 00 8B F2 03 D1 
3B 16 18 00 76 06 8B 0E 18 00 2B CE 33 D2 F7 36 1A 00 88 16 E9 01 8B F8 8B D7 51 8A C1 
8D 4C 01 C0 E6 06 0A CE 8A EA 8B 16 E8 01 B4 02 CD 13 59 73 15 80 FC 09 75 0A 49 EB DE 
8A C4 04 30 E8 18 FF B4 00 CD 13 EB D1 58 5A 03 C1 83 D2 00 2B E9 74 07 C1 E1 09 03 D9 
EB 94 C3 00 00 00 00 FA FC E8 00 00 5E 81 EE 85 01 2E 8B 84 E4 01 8E D8 8E C0 8E D0 2E 
C7 84 7C 01 AF 01 2E 89 84 7E 01 B9 00 01 BF 00 00 F3 2E A5 2E FF AC 7C FF BC 00 0A FB 
80 3E E8 01 FF 75 04 88 16 E8 01 83 06 E4 01 20 A1 E0 01 8B 16 E2 01 BD 02 00 E8 E9 FE 
50 52 EB 74 90 00 00 00 00 00 00 00 00 00 00 00 D3 20 00 00 00 30 80 00 FF 00 68 41 00 
40 09 FF 40 5A AC 04 00 00 AC 04 00 00 00 00 12 00 55 AA

这是从完全相同的CF卡中获取的,没有进行任何编辑/写入等操作,所以我对它们为什么如此不同感到困惑,但两者都以正确的55 AA字节结尾。当以这种方式访问卡时,Windows是否会损坏卡上的MBR,或者是否发生了其他我不知道的奇怪情况?

我假设从你的C#代码中调用cygwin的“dd”是不可行的? - Greg
这将是最后的选择,我认为问题在于将 //./F: 路径转换为 cygwin 可以理解的内容。因此,对我来说问题变成了如何在 c# 应用程序中将 //./F: 路径转换为 cygwin 中的 /dev/sdX。 - rb_
1
@Kragen,我认为这里发生的情况是Windows版本获取了卷引导记录而不是主引导记录。这可能是因为我传递给它的是//./F:,而F:只是第一个分区,而不是物理磁盘。在我进入下一个关于如何访问物理磁盘(而不是分区/卷)的问题之前,希望有人能确认我是否正确。 - rb_
只翻译文本内容,不解释。 - shelleybutterfly
@rb_ 对我来说听起来很准确 - 查看这两个样本,第一个(由dd生成)看起来像MBR(磁盘的前512个字节),而第二个看起来像引导记录(分区的前512个字节)。两者都看起来是有效且完全不同的引导扇区 - 没有办法通过编程错误意外地将一个搞乱成另一个。 - Justin
2个回答

6

我认为你所拥有的应该可以使用 - 我自己尝试过使用可引导的软盘映像(使用 ImDisk 挂载为虚拟驱动器),生成的文件与原始映像是二进制相同的。

为了完整起见,这是我使用的代码(完整版):

using System;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

namespace ConsoleApplication1
{
    public class Program
    {
        const int FILE_ATTRIBUTE_SYSTEM = 0x4;
        const int FILE_FLAG_SEQUENTIAL_SCAN = 0x8;

        [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern SafeFileHandle CreateFile(string fileName, [MarshalAs(UnmanagedType.U4)] FileAccess fileAccess, [MarshalAs(UnmanagedType.U4)] FileShare fileShare, IntPtr securityAttributes, [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition, int flags, IntPtr template);

        [STAThread]
        static void Main()
        {
            using (SafeFileHandle device = CreateFile(@"\\.\E:", FileAccess.Read, FileShare.Write | FileShare.Read | FileShare.Delete, IntPtr.Zero, FileMode.Open, FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_SEQUENTIAL_SCAN, IntPtr.Zero))
            {
                if (device.IsInvalid)
                {
                    throw new IOException("Unable to access drive. Win32 Error Code " + Marshal.GetLastWin32Error());
                }
                using (FileStream dest = File.Open("TempFile.bin", FileMode.Create))
                {
                    using (FileStream src = new FileStream(device, FileAccess.Read))
                    {
                        src.CopyTo(dest);
                    }
                }
            }
        }
    }
}

如果这样做不起作用,那么似乎表明:
  1. 原始图像存在问题。
  2. 使用你刚刚写入的磁盘映像的任何东西都存在问题。
  3. 在处理访问特定设备时存在一些微妙的差异(尽管我想不出来是什么)。
最有可能的罪魁祸首是第二步。你究竟是用生成的磁盘映像做了什么?

更新: 这是在评论中写的,但为了完整起见,我想将其添加到我的答案中 - 看起来正在发生的是正在写入磁盘的第一个分区的内容,而实际上想要的是整个磁盘的内容。

当您在HxD等软件中查看第二个十六进制字符串(由示例代码生成的字符串)时,我们会看到这样:

ëv.MSDOS5.0..........øò.?.ÿ.?...aÈ..€.)zè!.NO NAME    FAT16   ..
........................................................é..´.S3Û
Í.[Ê.<.t.èîÿCëôÃ.No BPB: Can't boot using CHS functions.P°.è¼ÿX
3ÛŽ.ä.ö.Ü..uBö.Ü..u.€>è.€r4SSRP.SUj.‹ôRPŠ.è.¸.BùÍ.ŠìXZ.d.r.€ý.u.
.ŃÒ.û‘.èxÿôëýƒ>...tðRP‹Í÷6..‹ò.Ñ;...v.‹...+Î3Ò÷6..ˆ.é.‹ø‹×QŠÁ.
L.Àæ..Ίê‹.è.´.Í.Ys.€ü.u.IëÞŠÄ.0è.ÿ´.Í.ëÑXZ.ÁƒÒ.+ét.Áá..Ùë”Ã....
úüè..^.î…..‹„ä.ŽØŽÀŽÐ.Ç„|.¯..‰„~.¹..¿..ó.¥.ÿ¬|ÿ¼..û€>è.ÿu.ˆ.è.ƒ.
ä. ¡à.‹.â.½..èéþPRët............Ó ...0€.ÿ.hA.@.ÿ@Z¬...¬.......Uª

这看起来像是FAT16分区的引导扇区 - 开头附近出现的字符串"MSDOS5.0"、"NO NAME"和"FAT16"是一个明显的线索。
与第一个十六进制字符串(由dd生成的字符串)的输出进行比较:
3ÀúŽØŽÐ¼.|‰æ.WŽÀûü¿..¹..ó¥ê....RR´A»ªU1É0öùÍ.r..ûUªu.Ñés.fÇ...´B
ë.Z´.Í.ƒá?Q.¶Æ@÷áRPf1Àf™èf.è!.Missing operating system...f`f1Ò».
|fRfP.Sj.j.‰æf÷6ô{Àä.ˆáˆÅ’ö6ø{ˆÆ.áA¸..Š.ú{Í..d.faÃèÄÿ¾¾}¿¾.¹ .ó¥
Ãf`‰å»¾.¹..1ÀSQö.€t.@‰ÞƒÃ.âóHt[y9Y[ŠG.<.t.$.<.u"f‹G.f‹V.f.Ðf!Òu.
f‰Âè¬ÿr.è¶ÿf‹F.è ÿƒÃ.âÌfaÃèb.Multiple active partitions...f‹D.f.
F.f‰D.è0ÿr..>þ}Uª.….ÿ¼ú{Z_.úÿäè..Operating system load error...^
¬´.Š>b.³.Í.<.uñÍ.ôëý......................................Ÿ)..€.
...þ?.?...aÈ..................................................Uª

我们看到的东西,对我来说很像一个主引导记录。为什么呢?因为在MBR中,前440个字节都是引导代码,而不像FAT引导扇区包含独特的BIOS参数块(它看起来像垃圾,但如果你将其通过反汇编器进行反汇编,你会得到一些看起来像有效的16位代码)。
此外,这两者看起来都是有效且完全不同的引导扇区(包括错误消息)。编程错误不可能使一个看起来像另一个的扇区变形 - 必须只是读取了错误的内容。
为了让CreateFile返回磁盘而不是分区,看起来你只需要传递一个不同的字符串,例如@"\\.\PhysicalDrive0"打开第一块物理磁盘。
参见:

一些背景信息,将更详细地解释我正在做什么。我们的旧系统过去是从格式化为FAT的CF卡引导的,因此要更新系统上的软件,您只需使用Windows应用程序重新刷写CF卡即可。新系统不支持从CF卡引导,但它确实有一个硬盘和一个USB端口。因此,计划是像以前一样使用Windows应用程序刷新CF卡,然后在Windows中使用我的C#应用程序对该CF卡进行映像,将其复制到Live USB上,然后使用dd从Live Linux环境闪存硬盘,使用CF卡映像。 - rb_
我认为这不是第二步,因为如果我将使用Linux VM和dd从CF创建的图像放在Live USB上,它确实可以工作。 - rb_
@rb_ 7 哦,我明白了 - 所以这个镜像最终被写入硬盘。在这种情况下,我不确定为什么它不起作用 - 只要第一个扇区(512字节)以“有效的引导扇区”签名字节(0x55、0xAA)结束,BIOS就应该使用该引导扇区进行引导。我的猜测是,BIOS实际上正在使用该引导扇区,但硬件差异意味着启动加载程序(在旧系统上之前工作)在新系统上失败了。 - Justin
请提供完整的工作代码,包括命名空间,以免需要寻找。♡ - shelleybutterfly
@Kragen 很酷,谢谢你再次向我保证,我想我需要问另一个问题(在一点点的谷歌搜索之后),关于如何使用WMI(或类似的东西)来获取从F:\到\.\PhysicalDriveX的映射。 - rb_

1
这是我编写的代码,用于获取给定驱动器字母的 \.\PhysicalDriveX 路径。如果将驱动器字母传递给此函数并使用返回值作为 CreateFile 的第一个参数,那么现在应该可以类似于 Linux 下的 dd 命令了。
using System.Management; //Add in a reference to this as well in the project settings
public static string GetPhysicalDevicePath(char DriveLetter)
{
    ManagementClass devs = new ManagementClass( @"Win32_Diskdrive");
    {
        ManagementObjectCollection moc = devs.GetInstances();
        foreach(ManagementObject mo in moc)
        {
            foreach (ManagementObject b in mo.GetRelated("Win32_DiskPartition"))
            {
                foreach (ManagementBaseObject c in b.GetRelated("Win32_LogicalDisk"))
                {
                    string DevName = string.Format("{0}", c["Name"]);
                    if (DevName[0] == DriveLetter)
                        return string.Format("{0}", mo["DeviceId"]); 
                }
            }
        }
    }
    return "";
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接