diff --git a/DiskImager.Installer/DiskImager.Installer.vdproj b/DiskImager.Installer/DiskImager.Installer.vdproj index fefc81b..b28bdef 100644 --- a/DiskImager.Installer/DiskImager.Installer.vdproj +++ b/DiskImager.Installer/DiskImager.Installer.vdproj @@ -62,6 +62,19 @@ "PrerequisitesLocation" = "2:1" "Url" = "8:" "ComponentsUrl" = "8:" + "Items" + { + "{EDC2488A-8267-493A-A98E-7D9C3B36CDF3}:.NETFramework,Version=v4.0,Profile=Client" + { + "Name" = "8:Microsoft .NET Framework 4 Client Profile (x86 and x64)" + "ProductCode" = "8:.NETFramework,Version=v4.0,Profile=Client" + } + "{EDC2488A-8267-493A-A98E-7D9C3B36CDF3}:Microsoft.Windows.Installer.3.1" + { + "Name" = "8:Windows Installer 3.1" + "ProductCode" = "8:Microsoft.Windows.Installer.3.1" + } + } } } "Release" @@ -86,6 +99,19 @@ "PrerequisitesLocation" = "2:1" "Url" = "8:" "ComponentsUrl" = "8:" + "Items" + { + "{EDC2488A-8267-493A-A98E-7D9C3B36CDF3}:.NETFramework,Version=v4.0,Profile=Client" + { + "Name" = "8:Microsoft .NET Framework 4 Client Profile (x86 and x64)" + "ProductCode" = "8:.NETFramework,Version=v4.0,Profile=Client" + } + "{EDC2488A-8267-493A-A98E-7D9C3B36CDF3}:Microsoft.Windows.Installer.3.1" + { + "Name" = "8:Windows Installer 3.1" + "ProductCode" = "8:Microsoft.Windows.Installer.3.1" + } + } } } } @@ -203,15 +229,15 @@ { "Name" = "8:Microsoft Visual Studio" "ProductName" = "8:Disk Imager" - "ProductCode" = "8:{4B773E0E-8A61-4441-9E75-7D48A42C7581}" - "PackageCode" = "8:{1346B501-A133-428C-9788-B6BD06B7B55B}" + "ProductCode" = "8:{E986C6D0-49F6-4FA9-BA4E-C4B25069CB4E}" + "PackageCode" = "8:{4D46D4A9-7CFC-46C6-9539-5C2365A79E66}" "UpgradeCode" = "8:{A2F957D8-23F6-44DB-A22C-CCA8275E1FCE}" "AspNetVersion" = "8:4.0.30319.0" "RestartWWWService" = "11:FALSE" "RemovePreviousVersions" = "11:FALSE" "DetectNewerInstalledVersion" = "11:TRUE" "InstallAllUsers" = "11:FALSE" - "ProductVersion" = "8:1.0.1" + "ProductVersion" = "8:1.0.2" "Manufacturer" = "8:Dynamic Devices" "ARPHELPTELEPHONE" = "8:" "ARPHELPLINK" = "8:" @@ -779,6 +805,34 @@ { } } + "{5259A561-127C-4D43-A0A1-72F10C7B3BF8}:_9B462EF766884DF9B8B000AB7F833D6D" + { + "SourcePath" = "8:" + "TargetName" = "8:" + "Tag" = "8:" + "Folder" = "8:_072AE4B10D3C471AA2B16D68E3691837" + "Condition" = "8:" + "Transitive" = "11:FALSE" + "Vital" = "11:TRUE" + "ReadOnly" = "11:FALSE" + "Hidden" = "11:FALSE" + "System" = "11:FALSE" + "Permanent" = "11:FALSE" + "SharedLegacy" = "11:FALSE" + "PackageAs" = "3:1" + "Register" = "3:1" + "Exclude" = "11:FALSE" + "IsDependency" = "11:FALSE" + "IsolateTo" = "8:" + "ProjectOutputGroupRegister" = "3:1" + "OutputConfiguration" = "8:" + "OutputGroupCanonicalName" = "8:ContentFiles" + "OutputProjectGuid" = "8:{4A73C63C-2BF2-4F85-AA55-A5CA581A33B4}" + "ShowKeyOutput" = "11:TRUE" + "ExcludeFilters" + { + } + } } } } diff --git a/DiskImager/Disk.cs b/DiskImager/Disk.cs index 5c56842..c91cf0b 100644 --- a/DiskImager/Disk.cs +++ b/DiskImager/Disk.cs @@ -1,7 +1,11 @@ using System; using System.IO; +using System.Linq; using System.Management; using System.Runtime.InteropServices; +using ICSharpCode.SharpZipLib.GZip; +using ICSharpCode.SharpZipLib.Tar; +using ICSharpCode.SharpZipLib.Zip; using Microsoft.Win32.SafeHandles; namespace DynamicDevices.DiskWriter @@ -23,12 +27,12 @@ namespace DynamicDevices.DiskWriter /// /// /// + /// /// - public bool WriteDrive(string driveLetter, string fileName) + public bool WriteDrive(string driveLetter, string fileName, EnumCompressionType eCompType) { var success = false; int intOut; - long driveSize = 0; IsCancelling = false; @@ -52,45 +56,47 @@ namespace DynamicDevices.DiskWriter "\"} where assocclass=Win32_LogicalDiskToPartition"); searcher = new ManagementObjectSearcher(scope, associators); var disks = searcher.Get(); - foreach (ManagementObject disk in disks) + if ( + !(from ManagementObject disk in disks select (string) disk["deviceid"]).Any( + thisDisk => thisDisk == driveLetter)) continue; + diskIndex = (int)(UInt32)current["diskindex"]; ; + + // + // Unmount partition (Todo: Note that we currntly only handle unmounting of one partition, which is the usual case for SD Cards) + // + + // + // Open the volume + /// + partitionHandle = NativeMethods.CreateFile(@"\\.\" + driveLetter, NativeMethods.GENERIC_READ, NativeMethods.FILE_SHARE_READ, IntPtr.Zero, NativeMethods.OPEN_EXISTING, 0, IntPtr.Zero); + if (partitionHandle.IsInvalid) { - var thisDisk = (string)disk["deviceid"]; - if (thisDisk == driveLetter) - { - // Grab physical drive and size - diskIndex = (int)(UInt32)current["diskindex"]; ; + OnLogMsg(this, @"Failed to open device"); + partitionHandle.Dispose(); + return false; + } - // Unmount partition (todo: Note that we currntly only handle unmounting of one partition, which is the usual case for SD Cards) - partitionHandle = NativeMethods.CreateFile(@"\\.\" + driveLetter, NativeMethods.GENERIC_READ, NativeMethods.FILE_SHARE_READ, IntPtr.Zero, NativeMethods.OPEN_EXISTING, 0, IntPtr.Zero); - if (partitionHandle.IsInvalid) - { - OnLogMsg(this, @"Failed to open device"); -// NativeMethods.CloseHandle(partitionHandle); - partitionHandle.Dispose(); - return false; - } + // + // Lock it + // + success = NativeMethods.DeviceIoControl(partitionHandle, NativeMethods.FSCTL_LOCK_VOLUME, null, 0, null, 0, out intOut, IntPtr.Zero); + if (!success) + { + OnLogMsg(this, @"Failed to lock device"); + partitionHandle.Dispose(); + return false; + } - success = NativeMethods.DeviceIoControl(partitionHandle, NativeMethods.FSCTL_LOCK_VOLUME, null, 0, null, 0, out intOut, IntPtr.Zero); - if (!success) - { - OnLogMsg(this, @"Failed to lock device"); -// NativeMethods.CloseHandle(partitionHandle); - partitionHandle.Dispose(); - return false; - } - - success = NativeMethods.DeviceIoControl(partitionHandle, NativeMethods.FSCTL_DISMOUNT_VOLUME, null, 0, null, 0, out intOut, IntPtr.Zero); - if (!success) - { - OnLogMsg(this, @"Error dismounting volume: " + Marshal.GetHRForLastWin32Error()); - NativeMethods.DeviceIoControl(partitionHandle, NativeMethods.FSCTL_UNLOCK_VOLUME, null, 0, null, 0, out intOut, IntPtr.Zero); -// NativeMethods.CloseHandle(partitionHandle); - partitionHandle.Dispose(); - return false; - } - - break; - } + // + // Dismount it + // + success = NativeMethods.DeviceIoControl(partitionHandle, NativeMethods.FSCTL_DISMOUNT_VOLUME, null, 0, null, 0, out intOut, IntPtr.Zero); + if (!success) + { + OnLogMsg(this, @"Error dismounting volume: " + Marshal.GetHRForLastWin32Error()); + NativeMethods.DeviceIoControl(partitionHandle, NativeMethods.FSCTL_UNLOCK_VOLUME, null, 0, null, 0, out intOut, IntPtr.Zero); + partitionHandle.Dispose(); + return false; } } @@ -104,6 +110,9 @@ namespace DynamicDevices.DiskWriter var physicalDrive = @"\\.\PhysicalDrive" + diskIndex; + // + // Now that we've dismounted the logical volume mounted on the removable drive we can open up the physical disk to write + // var diskHandle = NativeMethods.CreateFile(physicalDrive, NativeMethods.GENERIC_WRITE, 0, IntPtr.Zero, NativeMethods.OPEN_EXISTING, 0, IntPtr.Zero); if (diskHandle.IsInvalid) { @@ -115,13 +124,12 @@ namespace DynamicDevices.DiskWriter // Get drive size (NOTE: that WMI and IOCTL_DISK_GET_DRIVE_GEOMETRY don't give us the right value so we do it this way) // - driveSize = GetDiskSize(diskHandle); + var driveSize = GetDiskSize(diskHandle); success = NativeMethods.DeviceIoControl(diskHandle, NativeMethods.FSCTL_LOCK_VOLUME, null, 0, null, 0, out intOut, IntPtr.Zero); if (!success) { OnLogMsg(this, @"Failed to lock device"); -// NativeMethods.CloseHandle(diskHandle); diskHandle.Dispose(); return false; } @@ -129,28 +137,128 @@ namespace DynamicDevices.DiskWriter var buffer = new byte[Globals.MaxBufferSize]; long offset = 0; - using (var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read)) + using (var basefs = new FileStream(fileName, FileMode.Open, FileAccess.Read)) { - using (var bw = new BinaryReader(fs)) + + Stream fs; + + switch (eCompType) + { + case EnumCompressionType.Zip: + { + var zipFile = new ZipFile(basefs); + + Stream zis = null; + + foreach(ZipEntry zipEntry in zipFile) + { + if (!zipEntry.IsFile) + continue; + + zis = zipFile.GetInputStream(zipEntry); + break; + } + + if(zis == null) + { + OnLogMsg(this, @"Error reading zip input stream"); + goto readfail2; + } + + fs = zis; + } + break; + + case EnumCompressionType.Gzip: + { + var gzis = new GZipInputStream(basefs) {IsStreamOwner = true}; + + fs = gzis; + } + break; + + case EnumCompressionType.Targzip: + { + var gzos = new GZipInputStream(basefs); + gzos.IsStreamOwner = true; + + var tis = new TarInputStream(gzos); + + TarEntry tarEntry; + do + { + tarEntry = tis.GetNextEntry(); + } while (tarEntry.IsDirectory); + + fs = tis; + } + break; + + default: + + // No compression - direct to file stream + fs = basefs; + break; + } + + var bufferOffset = 0; + + using (var br = new BinaryReader(fs)) { while (offset < driveSize && !IsCancelling) { - var readBytes = bw.Read(buffer, 0, buffer.Length); - + // Note: There's a problem writing certain lengths to the underlying physical drive. + // This appears when we try to read from a compressed stream as it gives us + // "strange" lengths which then fail to be written via Writefile() so try to build + // up a decent block of bytes here... + int readBytes = 0; + do + { + readBytes = br.Read(buffer, bufferOffset, buffer.Length - bufferOffset); + bufferOffset += readBytes; + } while (bufferOffset < Globals.MaxBufferSize && readBytes != 0); + int wroteBytes; + var bytesToWrite = bufferOffset; + var trailingBytes = 0; - if (NativeMethods.WriteFile(diskHandle, buffer, readBytes, out wroteBytes, IntPtr.Zero) < 0) + // Assume that the underlying physical drive will at least accept powers of two! + if(!IsPowerOfTwo((ulong)bufferOffset)) + { + // Find highest bit (32-bit max) + var highBit = 31; + for (; ((bufferOffset & (1 << highBit)) == 0) && highBit >= 0; highBit--) + ; + + // Work out trailing bytes after last power of two + var lastPowerOf2 = 1 << highBit; + + bytesToWrite = lastPowerOf2; + trailingBytes = bufferOffset - lastPowerOf2; + } + + if (NativeMethods.WriteFile(diskHandle, buffer, bytesToWrite, out wroteBytes, IntPtr.Zero) < 0) { OnLogMsg(this, @"Error writing data to drive: " + Marshal.GetHRForLastWin32Error()); goto readfail1; } - if (wroteBytes != readBytes) + if (wroteBytes != bytesToWrite) { OnLogMsg(this, @"Error writing data to drive - past EOF?"); goto readfail1; } + // Move trailing bytes up - Todo: Suboptimal + if (trailingBytes > 0) + { + Buffer.BlockCopy(buffer, bufferOffset - trailingBytes, buffer, 0, trailingBytes); + bufferOffset = trailingBytes; + } + else + { + bufferOffset = 0; + } offset += (uint)wroteBytes; var percentDone = (int)(100 * offset / driveSize); @@ -166,23 +274,14 @@ namespace DynamicDevices.DiskWriter } } - success = true; - readfail1: NativeMethods.DeviceIoControl(diskHandle, NativeMethods.FSCTL_UNLOCK_VOLUME, null, 0, null, 0, out intOut, IntPtr.Zero); - readfail2: - if (diskHandle != null) - { -// NativeMethods.CloseHandle(diskHandle); - diskHandle.Dispose(); - } + readfail2: + diskHandle.Dispose(); readfail3: if (partitionHandle != null) - { -// NativeMethods.CloseHandle(partitionHandle); partitionHandle.Dispose(); - } var tstotalTime = DateTime.Now.Subtract(dtStart); @@ -199,12 +298,12 @@ namespace DynamicDevices.DiskWriter /// /// /// + /// /// - public bool ReadDrive(string driveLetter, string fileName) + public bool ReadDrive(string driveLetter, string fileName, EnumCompressionType eCompType) { var success = false; int intOut; - long driveSize = 0; IsCancelling = false; @@ -235,11 +334,11 @@ namespace DynamicDevices.DiskWriter // // Get drive size (NOTE: that WMI and IOCTL_DISK_GET_DRIVE_GEOMETRY don't give us the right value so we do it this way) // - driveSize = GetDiskSize(diskHandle); + var driveSize = GetDiskSize(diskHandle); if(driveSize <= 0) { OnLogMsg(this, @"Failed to get device size"); - NativeMethods.CloseHandle(diskHandle); + diskHandle.Dispose(); return false; } @@ -250,7 +349,7 @@ namespace DynamicDevices.DiskWriter if (!success) { OnLogMsg(this, @"Failed to lock device"); - NativeMethods.CloseHandle(diskHandle); + diskHandle.Dispose(); return false; } @@ -261,8 +360,75 @@ namespace DynamicDevices.DiskWriter var buffer = new byte[Globals.MaxBufferSize]; var offset = 0L; - using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write)) + + using(var basefs = (Stream)new FileStream(fileName, FileMode.Create, FileAccess.Write)) { + Stream fs; + + switch (eCompType) + { + case EnumCompressionType.Zip: + { + var zfs = new ZipOutputStream(basefs); + + // Default to middle of the range compression + zfs.SetLevel(Globals.CompressionLevel); + + var fi = new FileInfo(fileName); + var entryName = fi.Name; + entryName = entryName.ToLower().Replace(".zip", ""); + entryName = ZipEntry.CleanName(entryName); + var zipEntry = new ZipEntry(entryName) {DateTime = fi.LastWriteTime}; + zfs.IsStreamOwner = true; + + // Todo: Consider whether size needs setting for older utils ? + + zfs.PutNextEntry(zipEntry); + + fs = zfs; + } + break; + + case EnumCompressionType.Gzip: + { + var gzos = new GZipOutputStream(basefs); + gzos.SetLevel(Globals.CompressionLevel); + gzos.IsStreamOwner = true; + + fs = gzos; + } + break; + + case EnumCompressionType.Targzip: + { + var gzos = new GZipOutputStream(basefs); + gzos.SetLevel(Globals.CompressionLevel); + gzos.IsStreamOwner = true; + + var tos = new TarOutputStream(gzos); + + var fi = new FileInfo(fileName); + var entryName = fi.Name; + entryName = entryName.ToLower().Replace(".tar.gz", ""); + entryName = entryName.ToLower().Replace(".tgz", ""); + + var tarEntry = TarEntry.CreateTarEntry(entryName); + tarEntry.Size = driveSize; + tarEntry.ModTime = DateTime.SpecifyKind(fi.LastWriteTime, DateTimeKind.Utc); + + tos.PutNextEntry(tarEntry); + + fs = tos; + } + break; + + default: + + // No compression - direct to file stream + fs = basefs; + break; + } + using (var bw = new BinaryWriter(fs)) { while (offset < driveSize && !IsCancelling) @@ -271,13 +437,17 @@ namespace DynamicDevices.DiskWriter // seem to do a partial read. Deal with this by reading the remaining bytes at the end of the // drive if necessary - var readMaxLength = (int)((((ulong)driveSize - (ulong)offset) < (ulong)buffer.Length) ? ((ulong)driveSize - (ulong)offset) : (ulong)buffer.Length); + var readMaxLength = + (int) + ((((ulong) driveSize - (ulong) offset) < (ulong) buffer.Length) + ? ((ulong) driveSize - (ulong) offset) + : (ulong) buffer.Length); int readBytes; if (NativeMethods.ReadFile(diskHandle, buffer, readMaxLength, out readBytes, IntPtr.Zero) < 0) { OnLogMsg(this, @"Error reading data from drive: " + - Marshal.GetHRForLastWin32Error()); + Marshal.GetHRForLastWin32Error()); goto readfail1; } @@ -290,27 +460,32 @@ namespace DynamicDevices.DiskWriter goto readfail1; } - offset += (uint)readBytes; + offset += (uint) readBytes; - var percentDone = (int)(100 * offset / driveSize); + var percentDone = (int) (100*offset/driveSize); var tsElapsed = DateTime.Now.Subtract(dtStart); - var bytesPerSec = offset / tsElapsed.TotalSeconds; + var bytesPerSec = offset/tsElapsed.TotalSeconds; OnProgress(this, percentDone); - OnLogMsg(this, @"Read " + percentDone + @"%, " + (offset / (1024 * 1024)) + @" MB / " + - (driveSize / (1024 * 1024) + " MB, " + - string.Format("{0:F}", (bytesPerSec / (1024 * 1024))) + @" MB/sec, Elapsed time: " + tsElapsed.ToString(@"dd\.hh\:mm\:ss"))); + OnLogMsg(this, @"Read " + percentDone + @"%, " + (offset/(1024*1024)) + @" MB / " + + (driveSize/(1024*1024) + " MB, " + + string.Format("{0:F}", (bytesPerSec/(1024*1024))) + @" MB/sec, Elapsed time: " + + tsElapsed.ToString(@"dd\.hh\:mm\:ss"))); } + + // Todo: Do we need this? + if(fs is ZipOutputStream) + ((ZipOutputStream)fs).CloseEntry(); + if(fs is TarOutputStream) + ((TarOutputStream)fs).CloseEntry(); } - } - success = true; + } readfail1: NativeMethods.DeviceIoControl(diskHandle, NativeMethods.FSCTL_UNLOCK_VOLUME, null, 0, null, 0, out intOut, IntPtr.Zero); readfail2: -// NativeMethods.CloseHandle(diskHandle); diskHandle.Dispose(); readfail3: var tstotalTime = DateTime.Now.Subtract(dtStart); @@ -330,7 +505,7 @@ namespace DynamicDevices.DiskWriter /// /// /// - private int GetDiskIndex(string driveLetter) + private static int GetDiskIndex(string driveLetter) { int diskIndex = -1; @@ -346,16 +521,10 @@ namespace DynamicDevices.DiskWriter "\"} where assocclass=Win32_LogicalDiskToPartition"); searcher = new ManagementObjectSearcher(scope, associators); var disks = searcher.Get(); - foreach (ManagementObject disk in disks) - { - var thisDisk = (string)disk["deviceid"]; - if (thisDisk == driveLetter) - { - // Grab physical drive and size - diskIndex = (int)(UInt32)current["diskindex"]; ; - break; - } - } + if ( + !(from ManagementObject disk in disks select (string) disk["deviceid"]).Any( + thisDisk => thisDisk == driveLetter)) continue; + diskIndex = (int)(UInt32)current["diskindex"]; } return diskIndex; @@ -366,7 +535,7 @@ namespace DynamicDevices.DiskWriter /// /// /// - private long GetDiskSize(SafeFileHandle diskHandle) + private static long GetDiskSize(SafeFileHandle diskHandle) { long size = -1; @@ -387,6 +556,11 @@ namespace DynamicDevices.DiskWriter return size; } + bool IsPowerOfTwo(ulong x) + { + return (x != 0) && ((x & (x - 1)) == 0); + } + #endregion } } \ No newline at end of file diff --git a/DiskImager/DiskImager.csproj b/DiskImager/DiskImager.csproj index 2318d65..49d3127 100644 --- a/DiskImager/DiskImager.csproj +++ b/DiskImager/DiskImager.csproj @@ -1,4 +1,4 @@ - + Debug @@ -13,6 +13,21 @@ v4.0 Client 512 + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true x86 @@ -42,6 +57,13 @@ Always + + false + + + + + ..\Libs\SharpZipLib\SharpZipLib.dll @@ -61,6 +83,7 @@ + Form @@ -99,6 +122,28 @@ + + + False + Microsoft .NET Framework 4 Client Profile %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + false + + + False + Windows Installer 3.1 + true + +