Example #1
0
        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);
        }
Example #2
0
        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);
        }