public static Geometry FindGeometry(Stream stream) { var geometryType = "UNKN"; Geometry geometry = null; // Always check GPT first as it as an MBR-encapsulated partition scheme if (GuidPartitionTable.Detect(stream)) { geometry = GuidPartitionTable.DetectGeometry(stream); geometryType = "GPT"; } else if (BiosPartitionTable.IsValid(stream)) { geometry = BiosPartitionTable.DetectGeometry(stream); geometryType = "MBR"; } if (geometry == null) { Logger.Warn("Could not find valid partitioning table to read geometry"); return(Geometry.FromCapacity(stream.Length)); } Logger.Info("{0}/BPS:{1}; SPT:{2}; HPC:{3}; CL:{4}; TS:{5}; CP:{6}", geometryType, geometry.BytesPerSector, geometry.SectorsPerTrack, geometry.HeadsPerCylinder, geometry.Cylinders, geometry.TotalSectorsLong, geometry.Capacity); return(geometry); }
public static PartitionTable FindPartitionTable(Stream stream) { PartitionTable partitionTable = null; // Always check GPT first as it as an MBR-encapsulated partition scheme if (GuidPartitionTable.Detect(stream)) { partitionTable = new GuidPartitionTable( stream, GuidPartitionTable.DetectGeometry(stream)); Logger.Info("Detected partition table: GPT"); } else if (BiosPartitionTable.IsValid(stream)) { partitionTable = new BiosPartitionTable( stream, BiosPartitionTable.DetectGeometry(stream)); Logger.Info("Detected partition table: MBR"); } if (partitionTable == null) { Logger.Info("Could not find valid partition table"); return(null); } return(partitionTable); }
private static int StartInternal() { if (!GuidPartitionTable.Detect(mSourceStream)) { throw new InvalidDataException( "Source does not contain a supported partitioning scheme"); } Logger.Debug("Detecting disc geometry of source..."); var geometry = GuidPartitionTable.DetectGeometry(mSourceStream); Logger.Debug("Reading GPT partition table from source..."); var partitionTable = new GuidPartitionTable(mSourceStream, geometry); if (partitionTable.Count <= 0) { return(SUCCESS); // nothing to clone here } // adjust source length if (FixedLengthStream.IsFixedDiskStream(mSourceStream)) { mSourceStream.SetLength(geometry.Capacity); } Geometry destGeometry = null; if (FixedLengthStream.IsFixedDiskStream(mDestinationStream)) { // If we write to a disk, we need our own geometry for that destination destGeometry = Geometry.FromCapacity(mDestinationStream.Length, geometry.BytesPerSector); } else { // If we are just writing to a file, we take the most exact copy we get destGeometry = geometry; } // Set the new size of the destination stream Logger.Verbose("Updating length of destination: {0} -> {1}", mDestinationStream.Length, destGeometry.Capacity); mDestinationStream.SetLength(destGeometry.Capacity); if (mFullClone) { Logger.Warn("Starting full clone..."); CloneStream(0, 0, mSourceStream, mDestinationStream); return(SUCCESS); } Logger.Debug("Initializing new GPT partition table on destination..."); var destPartitionTable = GuidPartitionTable.Initialize(mDestinationStream, destGeometry); var availableSpace = destGeometry.Capacity; var usedSpace = 0L; // correct available space by remove unusable sectors // heading sectors availableSpace -= (destPartitionTable.FirstUsableSector * destGeometry.BytesPerSector); // ending sectors availableSpace -= ((destGeometry.TotalSectorsLong - destPartitionTable.FirstUsableSector - destPartitionTable.LastUsableSector) * destGeometry.BytesPerSector); // 1. calculate unresizeable space if we are required too if (destGeometry.Capacity < geometry.Capacity) { Logger.Info("Calculating space for new destination partitions..."); var unresizeableSpace = 0L; for (int i = 0; i < partitionTable.Count; i++) { var srcPartition = (GuidPartitionInfo)partitionTable[i]; var srcPartitionStream = srcPartition.Open(); var srcPartitionType = GetWellKnownPartitionType(srcPartition); var srcPartitionSize = srcPartition.SectorCount * geometry.BytesPerSector; var isResizeable = true; // If the file system is not support, we have to perform // a byte-for-byte cloning and cannot resize this partition if (!DiscHandler.IsStreamSupportedFileSystem(srcPartitionStream)) { Logger.Verbose("Partition{0}: contains no or unsupported file system"); isResizeable = false; } // If we have a critical partition, we shouldn't resize that either if (!IsSupportedPartitionType(srcPartitionType)) { Logger.Verbose("Partition{0}: unsupported partition type"); isResizeable = false; } // if caller want to do a byte-for-byte clone, mark every partition // as unresizeable if (mForceExactCloning) { isResizeable = false; } Logger.Debug("Partition #{0} on source: isResizeable={1}", i, isResizeable); if (!isResizeable) { // if it's not resizeable, account the space and continue unresizeableSpace += srcPartitionSize; Logger.Debug("Partition #{0} on source: unresizeable, size is {1} " + "(total unresizable {2}/used {3})", i, srcPartitionSize, unresizeableSpace, usedSpace); } else { // if it is resizeable, we have to report how much space we need var srcPartitionFS = DiscHandler.GetFileSystem(srcPartitionStream); usedSpace += srcPartitionFS.UsedSpace; Logger.Debug("Partition #{0} on source: resizeable, space is {1} " + "(total unresizable {2}/used {3})", i, usedSpace, unresizeableSpace, usedSpace); } } // reduce the dynamically available space ... availableSpace -= unresizeableSpace; // ... and assert it if (availableSpace < 0) { throw new InvalidOperationException("Cannot clone, destination is " + String.Format("{0:#,##0}", -availableSpace) + " Bytes too small"); } // assert the used space too if (availableSpace - usedSpace < 0) { throw new InvalidOperationException("Cannot clone, destination is " + String.Format("{0:#,##0}", availableSpace - usedSpace) + " Bytes too small"); } } else { Logger.Info("Destination can contain source, no need to resize partitions!"); } // 2. calculate the size for each new partition var destPartitionSizes = new long[partitionTable.Count]; for (int i = 0; i < partitionTable.Count; i++) { var srcPartition = (GuidPartitionInfo)partitionTable[i]; var srcPartitionStream = srcPartition.Open(); var srcPartitionType = GetWellKnownPartitionType(srcPartition); var srcPartitionSize = srcPartition.SectorCount * geometry.BytesPerSector; // if the destination can take more data, skip every check if (geometry.Capacity <= destGeometry.Capacity) { Logger.Debug("Partition{0}: Destination can contain full partition, continue...", i); destPartitionSizes[i] = srcPartitionSize; availableSpace -= srcPartitionSize; continue; } var isResizeable = true; // If the device-systme is not support, we have to perform // a byte-for-byte cloning and cannot resize this partition if (!DiscHandler.IsStreamSupportedFileSystem(srcPartitionStream)) { isResizeable = false; } // If we have a critical partition, we shouldn't resize that either if (!IsSupportedPartitionType(srcPartitionType)) { isResizeable = false; } // if caller want to do a byte-for-byte clone, mark every partition // as unresizeable if (mForceExactCloning) { isResizeable = false; } Logger.Debug("Partition #{0} on source: isResizeable={1}", i, isResizeable); // If our friend is not resizeable, set size and stop processing it if (!isResizeable) { destPartitionSizes[i] = srcPartitionSize; Logger.Debug("Partition #{0} on source: unresizeable, size is {1} " + "(total available {2}/used {3})", i, usedSpace, availableSpace, usedSpace); // DO NOT ALIGN <availableSpace> HERE!!! // Has already been done in step 1 continue; } // // OK. If we are here, a resizeable partition awaits processing // var srcPartitionFS = DiscHandler.GetFileSystem(srcPartitionStream); // First we need to check if we need to reqsize the current partition. // For that, calculate the space that is left for future partitions. If the // result is OK (less or equal to the available size), we can skip // resizing the the current one. // Make sure to remove the factor of this partition from usedSpace first if (((usedSpace - srcPartitionFS.UsedSpace) + srcPartitionSize) <= availableSpace) { // update usedSpace usedSpace -= srcPartitionFS.UsedSpace; // align availableSpace availableSpace -= srcPartitionSize; // we are good to skip resizing this one. assign size and early exit destPartitionSizes[i] = srcPartitionSize; Logger.Debug( "Partition #{0} on source: resizeable, space is {1}, size is {2} " + "(total available {3}/used {4})", i, srcPartitionFS.UsedSpace, srcPartitionSize, availableSpace, usedSpace); continue; } // So this partition is too large, let's resize it to the biggest size possible var maxPartitionSize = Math.Max( // Occupied space is the absolute minimal space we need srcPartitionFS.UsedSpace, // This is the largest space possible. Take the still available space // and remove still required space, while also remove the factor for // the current partition availableSpace - (usedSpace - srcPartitionFS.UsedSpace) ); Logger.Debug( "Partition #{0} on source: resizeable, max. space is {1} " + "(total available {2}/used {3})", i, maxPartitionSize, availableSpace, usedSpace); // update usedSpace usedSpace -= srcPartitionFS.UsedSpace; // align availableSpace availableSpace -= maxPartitionSize; destPartitionSizes[i] = maxPartitionSize; } // a last assert of the available space, just to be sure if (availableSpace < 0) { throw new InvalidOperationException("Cannot clone, destination is " + String.Format("{0:#.##0}", -availableSpace) + " Bytes too small"); } // 2. create the new partitions with the aligned sizes for (int i = 0; i < partitionTable.Count; i++) { var srcPartition = (GuidPartitionInfo)partitionTable[i]; var srcPartitionStream = srcPartition.Open(); var srcPartitionType = GetWellKnownPartitionType(srcPartition); // manueal type adjusting if (NtfsFileSystem.Detect(srcPartitionStream)) { srcPartitionType = WellKnownPartitionType.WindowsNtfs; } else if (FatFileSystem.Detect(srcPartitionStream)) { srcPartitionType = WellKnownPartitionType.WindowsFat; } var destPartitionSize = destPartitionSizes[i]; Logger.Debug("Creating partition table: #{0}; {1}/{2}@0x{3}-{4}", i, srcPartition.Name, srcPartition.Identity, srcPartition.FirstSector.ToString("X2"), destPartitionSize); destPartitionTable.Create( destPartitionSize, srcPartitionType, true //doesn't matter on GPT tables ); } // 3. make sure the count of partitions is the same if (partitionTable.Count != destPartitionTable.Count) { throw new InvalidOperationException( "Failed to create proper GUID partition table"); } // 4. do the real cloning for (int i = 0; i < destPartitionTable.Count; i++) { var srcPartition = (GuidPartitionInfo)partitionTable[i]; var srcPartitionStream = srcPartition.Open(); var srcPartitionType = GetWellKnownPartitionType(srcPartition); var destPartition = (GuidPartitionInfo)destPartitionTable[i]; var destPartitionStream = destPartition.Open(); var destPartitionSize = destPartitionSizes[i]; var requiresExactCloning = false; // To support all possible file-systems, perform a byte-for-byte // cloning if the file-system is not supported by us. if (!DiscHandler.IsStreamSupportedFileSystem(srcPartitionStream)) { requiresExactCloning = true; } // If we have a critical partition, we should skip that one too if (srcPartitionType == WellKnownPartitionType.BiosBoot || srcPartitionType == WellKnownPartitionType.EfiSystem || srcPartitionType == WellKnownPartitionType.MicrosoftReserved) { requiresExactCloning = true; } // if caller want to do a byte-for-byte clone, let's enable it if (mForceExactCloning) { requiresExactCloning = true; } if (requiresExactCloning) { var taskId = (mTaskIdCounter++); Logger.Info("[{0}/{1}] cp-fs : {2}/{3}@0x{4}", i, taskId, srcPartition.Name, srcPartition.Identity, srcPartition.FirstSector.ToString("X2")); CloneStream(i, taskId, srcPartitionStream, destPartitionStream); } else { var srcPartitionFS = DiscHandler.GetFileSystem(srcPartitionStream); var srcPartitionBootCode = srcPartitionFS.ReadBootCode(); var destPartitionFS = DiscHandler.FormatFileSystemWithTemplate( srcPartitionStream, destPartitionStream, destPartition.FirstSector, destPartition.SectorCount, destPartitionSize ); // Tracks all NTFS file IDs for hard link recognition // <Source FS File ID, Destination FS File ID> var ntfsHardlinkTracker = new Dictionary <uint, uint>(); // last clone each single with here Action <DiscDirectoryInfo, DiscDirectoryInfo> cloneSrcFsToDestFs = null; cloneSrcFsToDestFs = new Action <DiscDirectoryInfo, DiscDirectoryInfo>( (sourceDir, destDir) => { // recursive enumeration. save to create directory without checks for // parent directories. var taskId = (mTaskIdCounter++); Logger.Info("[{0}/{1}] mk-dir : {2}", i, taskId, destDir.FullName); destDir.Create(); // copy files if there are any foreach (var sourceFile in sourceDir.GetFiles()) { var skipCopying = false; taskId = (mTaskIdCounter++); var sourceFileStream = sourceFile.Open(FileMode.Open, FileAccess.Read); var destFileName = Path.Combine(destDir.FullName, sourceFile.Name); Logger.Info("[{0}/{1}] mk-file: {2}", i, taskId, destFileName); var destFileStream = destPartitionFS.OpenFile( destFileName, FileMode.Create, FileAccess.Write); var destFile = new DiscFileInfo(destPartitionFS, destFileName); // NTFS hard link handling if (destPartitionFS is NtfsFileSystem) { var ntfsSourceFS = (NtfsFileSystem)srcPartitionFS; var ntfsDestFS = (NtfsFileSystem)destPartitionFS; var sourceFileNtfsEntry = ntfsSourceFS.GetDirectoryEntry( sourceFile.FullName); var sourceFileNtfsRef = ntfsSourceFS.GetFile( sourceFileNtfsEntry.Reference); var destFileNtfsEntry = ntfsDestFS.GetDirectoryEntry( destFile.FullName); var destFileNtfsRef = ntfsDestFS.GetFile( destFileNtfsEntry.Reference); var sourceFileNtfsId = sourceFileNtfsRef.IndexInMft; // check if this files was already processed once if (ntfsHardlinkTracker.ContainsKey(sourceFileNtfsId)) { var trackedDestFileNtfsRef = ntfsDestFS.GetFile( ntfsHardlinkTracker[sourceFileNtfsId]); // if we have a hardlink-match, close the old stream // and delete the file destFileStream.Close(); destFile.Delete(); // then create the hardlink and mention that we don't // want/need to copy the content anymore Logger.Info("[{0}/{1}] mk-lnk : {2} => {3}", i, taskId, sourceFileNtfsRef.Names[0], destFile.FullName); ntfsDestFS.CreateHardLink( trackedDestFileNtfsRef.DirectoryEntry, destFile.FullName); skipCopying = true; } else { Logger.Verbose("[{0}/{1}] rg-lnk : {2}#{3} -> {4}#{5}", i, taskId, sourceFileNtfsRef.Names[0], sourceFileNtfsRef.IndexInMft, destFileNtfsRef.Names[0], destFileNtfsRef.IndexInMft); // if not, track it ntfsHardlinkTracker.Add(sourceFileNtfsRef.IndexInMft, destFileNtfsRef.IndexInMft); } } if (!skipCopying) { Logger.Info("[{0}/{1}] cp-file: {2}", i, taskId, destFile.FullName); CloneStream(i, taskId, sourceFileStream, destFileStream); } // clone basic file informationsdestFile Logger.Verbose("[{0}/{1}] cp-meta: {2}", i, taskId, destFile.FullName); destFile.Attributes = sourceFile.Attributes; destFile.CreationTime = sourceFile.CreationTime; destFile.CreationTimeUtc = sourceFile.CreationTimeUtc; destFile.IsReadOnly = sourceFile.IsReadOnly; destFile.LastAccessTime = sourceFile.LastAccessTime; destFile.LastAccessTimeUtc = sourceFile.LastAccessTimeUtc; destFile.LastWriteTime = sourceFile.LastWriteTime; destFile.LastWriteTimeUtc = sourceFile.LastWriteTimeUtc; // file-system based cloning if (destPartitionFS is NtfsFileSystem) { Logger.Verbose("[{0}/{1}] cp-fsec: {2}", i, taskId, destFile.FullName); var ntfsSourceFS = (NtfsFileSystem)srcPartitionFS; var ntfsDestFS = (NtfsFileSystem)destPartitionFS; var destFileNtfsEntry = ntfsDestFS.GetDirectoryEntry( destFile.FullName); var destFileNtfsRef = ntfsDestFS.GetFile( destFileNtfsEntry.Reference); // clone security descriptors var sourceNtfsSecurity = ntfsSourceFS.GetSecurity( sourceFile.FullName); ntfsDestFS.SetSecurity(destFile.FullName, sourceNtfsSecurity); // clone short names if destination file is not a hard link if (destFileNtfsEntry.Details.FileNameNamespace != FileNameNamespace.Posix || !destFileNtfsRef.HasWin32OrDosName) { Logger.Verbose("[{0}/{1}] cp-shrt: {2}", i, taskId, destFile.FullName); var sourceNtfsShortName = ntfsSourceFS.GetShortName( sourceFile.FullName); if (sourceNtfsShortName != null) { ntfsSourceFS.SetShortName(destFile.FullName, sourceNtfsShortName); } } } } // advance recursion into directories foreach (var sourceDirectory in sourceDir.GetDirectories()) { if (srcPartitionFS is FatFileSystem) { // Don't copy SYSTEM~1 on FAT. Just don't do it... if (sourceDirectory.Name.Equals("SYSTEM~1")) { continue; } } cloneSrcFsToDestFs(sourceDirectory, new DiscDirectoryInfo( destPartitionFS, Path.Combine(destDir.FullName, sourceDirectory.Name) ) ); } }); cloneSrcFsToDestFs(srcPartitionFS.Root, destPartitionFS.Root); } } return(SUCCESS); }
private static int StartInternal() { Info("====== Starting Partition Table Checks ======"); DoingCheck(); if (!GuidPartitionTable.Detect(mLeftStream)) { Error("Could not find valid GUID Partition Table on left stream"); return(ERROR); } Info("Found valid GUID Partition Table on left stream"); DoingCheck(); if (!GuidPartitionTable.Detect(mRightStream)) { Error("Could not find valid GUID Partition Table on right stream"); return(ERROR); } Info("Found valid GUID Partition Table on right stream"); var leftGeometry = GuidPartitionTable.DetectGeometry(mLeftStream); var leftPartitionTable = new GuidPartitionTable(mLeftStream, leftGeometry); var rightGeometry = GuidPartitionTable.DetectGeometry(mRightStream); var rightPartitionTable = new GuidPartitionTable(mRightStream, rightGeometry); // adjust length if (FixedLengthStream.IsFixedDiskStream(mLeftStream)) { mLeftStream.SetLength(leftGeometry.Capacity); } if (FixedLengthStream.IsFixedDiskStream(mRightStream)) { mRightStream.SetLength(leftGeometry.Capacity); } DoingCheck(); if (leftPartitionTable.Count != rightPartitionTable.Count) { Error("Non-equal count of partitions (Left: {0}, Right {1})", leftPartitionTable.Count, rightPartitionTable.Count); return(ERROR); } Info("Matching count of partitions ({0})", leftPartitionTable.Count); Info("====== Starting Partition Checks ======"); for (int i = 0; i < leftPartitionTable.Count; i++) { var leftPartition = leftPartitionTable[i]; var rightPartition = rightPartitionTable[i]; DoingCheck(); if (leftPartition.BiosType != rightPartition.BiosType) { Error("Partition{0}: Non-matching BIOS partition type " + "(Left: 0x{1:X2}, Right: 0x{2:X2})", i, leftPartition.BiosType, rightPartition.BiosType); return(ERROR); } Info("Partition{0}: Matching BIOS partition type (0x{1:X2})", i, leftPartition.BiosType); DoingCheck(); if (leftPartition.GuidType != rightPartition.GuidType) { Error("Partition{0}: Non-matching GUID partition type " + "(Left: {1}, Right: {2})", i, leftPartition.GuidType, rightPartition.GuidType); return(ERROR); } Info("Partition{0}: Matching GUID partition type ({1})", i, leftPartition.GuidType); DoingCheck(); if (leftPartition.VolumeType != rightPartition.VolumeType) { Error("Partition{0}: Non-matching physical partition type " + "(Left: 0x{1:X}/{1}, Right: 0x{2:X}/{2})", i, leftPartition.VolumeType, rightPartition.VolumeType); return(ERROR); } Info("Partition{0}: Matching physical partition type (0x{1:X}/{1})", i, leftPartition.VolumeType); } Info("====== Starting File System Checks ======"); for (int i = 0; i < leftPartitionTable.Count; i++) { var leftPartition = leftPartitionTable[i]; var leftPartitionStream = leftPartition.Open(); var leftPartitionValidFS = DiscHandler.IsStreamSupportedFileSystem(leftPartitionStream); var rightPartition = rightPartitionTable[i]; var rightPartitionStream = rightPartition.Open(); var rightPartitionValidFS = DiscHandler.IsStreamSupportedFileSystem(rightPartitionStream); if (!leftPartitionValidFS) { Info("Partition{0}: Left stream does not contain supported file system", i); } if (!rightPartitionValidFS) { Info("Partition{0}: Right stream does not contain supported file system", i); } if (leftPartitionValidFS && rightPartitionValidFS) { Info("Partition{0}: Running checks on file-system layer", i); var leftFileSystem = DiscHandler.GetFileSystem(leftPartitionStream); var rightFileSystem = DiscHandler.GetFileSystem(rightPartitionStream); if (leftFileSystem.UsedSpace != rightFileSystem.UsedSpace) { Warning("Partition{0}: Non-matching amount of used space " + "(Left: 0x{1:X2}/{2}, Right: 0x{3:X2}/{4})", i, leftFileSystem.UsedSpace, FormatBytes(leftFileSystem.UsedSpace, 3), rightFileSystem.UsedSpace, FormatBytes(rightFileSystem.UsedSpace, 3)); } Action <DiscDirectoryInfo, DiscDirectoryInfo> traverseFiles = null; traverseFiles = new Action <DiscDirectoryInfo, DiscDirectoryInfo>( (leftBaseDir, rightBaseDir) => { var leftFiles = leftBaseDir.GetFiles(); var rightFiles = rightBaseDir.GetFiles(); DoingCheck(); if (leftFiles.Length != rightFiles.Length) { Warning("Partition{0}: Non-matching amount of files in {1} " + "(Left: {2}, Right: {3})", i, leftBaseDir.FullName, leftFiles.Length, rightFiles.Length); var checkedFiles = new List <DiscFileInfo>(); foreach (var leftFile in leftFiles) { var matchingRightFiles = rightFiles.Where(f => f.Name == leftFile.Name); DoingCheck(); if (matchingRightFiles.Count() >= 2) { Error("Partition{0}:{1}: Found invalid file entry in " + "file system, looked up file with {2} results", i, leftBaseDir.FullName, matchingRightFiles.Count()); } DoingCheck(); if (matchingRightFiles.Count() == 1) { checkedFiles.Add(leftFile); var rightFile = matchingRightFiles.First(); var leftFileStream = leftFile.OpenRead(); var rightFileStream = rightFile.OpenRead(); DoByteComparison(leftFileStream, rightFileStream, i, leftFile.FullName); leftFileStream.Close(); rightFileStream.Close(); DoMetaDataComparison(i, leftFile, rightFile); if (leftFileSystem is NtfsFileSystem) { DoNtfsSecurityComparison(i, (NtfsFileSystem)leftFileSystem, leftFile, (NtfsFileSystem)rightFileSystem, rightFile); } } else // == 0 { Warning("Partition{0}:{1}: Found file in left " + "file system only", i, leftFile.FullName); } } foreach (var rightFile in rightFiles) { DoingCheck(); if (!checkedFiles.Any(f => f.Name == rightFile.Name)) { Warning("Partition{0}:{1}: Found file in right " + "file system only", i, rightFile.FullName); } } } else { for (int j = 0; j < leftFiles.Length; j++) { var leftFile = leftFiles[j]; var leftFileStream = leftFile.OpenRead(); var rightFile = rightFiles[j]; var rightFileStream = rightFile.OpenRead(); DoByteComparison(leftFileStream, rightFileStream, i, leftFile.FullName); leftFileStream.Close(); rightFileStream.Close(); DoMetaDataComparison(i, leftFile, rightFile); if (leftFileSystem is NtfsFileSystem) { DoNtfsSecurityComparison(i, (NtfsFileSystem)leftFileSystem, leftFile, (NtfsFileSystem)rightFileSystem, rightFile); } } } // advance recursion into directories var leftDirectories = leftBaseDir.GetDirectories(); var rightDirectories = rightBaseDir.GetDirectories(); DoingCheck(); if (leftDirectories.Length != rightDirectories.Length) { Warning("Partition{0}: Non-matching amount of directories in {1} " + "(Left: {2}, Right: {3})", i, leftBaseDir.FullName, leftDirectories.Length, rightDirectories.Length); var checkedDirectories = new List <DiscFileInfo>(); foreach (var leftDirectory in leftDirectories) { var matchingRightFiles = rightDirectories.Where(f => f.Name == leftDirectory.Name); DoingCheck(); if (matchingRightFiles.Count() >= 2) { Error("Partition{0}:{1}: Found invalid directory entry in " + "file system, looked up file with {2} results", i, leftBaseDir.FullName, matchingRightFiles.Count()); } DoingCheck(); if (matchingRightFiles.Count() == 1) { var rightDirectory = matchingRightFiles.First(); traverseFiles(leftDirectory, rightDirectory); } else // == 0 { Warning("Partition{0}:{1}: Found directory in left " + "file system only", i, leftDirectory.FullName); } } foreach (var rightFile in rightDirectories) { DoingCheck(); if (!checkedDirectories.Any(f => f.Name == rightFile.Name)) { Warning("Partition{0}:{1}: Found file in right " + "file system only", i, rightFile.FullName); } } } else { for (int j = 0; j < leftDirectories.Length; j++) { var leftDirectory = leftDirectories[j]; var rightDirectory = leftDirectories[j]; traverseFiles(leftDirectory, rightDirectory); } } }); traverseFiles(leftFileSystem.Root, rightFileSystem.Root); } else { Info("Partition{0}: Running checks on data layer", i); // This can can be run because of the fact that we don't resize // unsupported partitions but run byte-for-byte copy with them DoByteComparison(leftPartitionStream, rightPartitionStream, i, null); } } return(SUCCESS); }