├── .editorconfig
├── .gitattributes
├── .gitignore
├── ClonezillaApps.sln
├── LICENSE
├── README.md
├── clonezilla-util
├── CL
│ └── Verbs
│ │ ├── BaseVerb.cs
│ │ ├── ExtractFiles.cs
│ │ ├── ExtractPartitionImage.cs
│ │ ├── ListContents.cs
│ │ ├── MountAsFiles.cs
│ │ └── MountAsImageFiles.cs
├── Extensions.cs
├── Program.cs
└── clonezilla-util.csproj
├── clonezilla-util_tests
├── GlobalSuppressions.cs
├── GlobalUsings.cs
├── ListContents
│ ├── LargeClonezillaPartitions.cs
│ ├── LargeDriveImages.cs
│ ├── Partclone.cs
│ ├── SmallClonezillaPartitions.cs
│ ├── SmallPartitionImages.cs
│ └── TestUtility.cs
├── Main.cs
├── Mount
│ ├── AsFiles
│ │ ├── Ext4.cs
│ │ ├── LargeClonezillaImages.cs
│ │ ├── LargeDriveImages.cs
│ │ ├── LuksClonezillaImages.cs
│ │ ├── LuksParcloneImages.cs
│ │ ├── Misc.cs
│ │ ├── Partclone.cs
│ │ ├── SmallClonezillaPartitions.cs
│ │ ├── SmallPartitionImages.cs
│ │ └── UbuntuFileSystems.cs
│ ├── AsImageFiles
│ │ └── ImageFileTests.cs
│ └── TestUtility.cs
├── Sparse
│ └── SparseTests.cs
├── Train
│ └── TrainTests.cs
├── Utilities
│ └── Utility.cs
└── clonezilla-util_tests.csproj
├── lib7Zip
├── ArchiveEntry.cs
├── GlobalSuppressions.cs
├── SevenZipExtractorUsing7zFM.cs
├── SevenZipExtractorUsingSevenZipExtractor.cs
├── SevenZipExtractorUsingSevenZipSharp.cs
├── SevenZipUtility.cs
├── UI
│ ├── ListviewNative.cs
│ └── WinAPI.cs
├── ext
│ └── 7-Zip
│ │ ├── win-x64
│ │ ├── 7z.dll
│ │ ├── 7z.exe
│ │ └── 7zFM.exe
│ │ └── win-x86
│ │ ├── 7z.dll
│ │ ├── 7z.exe
│ │ └── 7zFM.exe
└── lib7Zip.csproj
├── libBzip2
├── BZip2BlockFinder.cs
├── Bzip2StreamSeekable.cs
└── libBzip2.csproj
├── libClonezilla
├── Cache
│ ├── ClonezillaCacheManager.cs
│ ├── FileSystem
│ │ ├── FileDetails.cs
│ │ ├── FileSystemObject.cs
│ │ └── FolderDetails.cs
│ ├── IClonezillaCacheManager.cs
│ ├── IPartitionCache.cs
│ ├── PartitionCache.cs
│ └── WholeFileCacheManager.cs
├── Decompressors
│ ├── Decompressor.cs
│ ├── DecompressorSelector.cs
│ ├── GzDecompressor.cs
│ ├── LZ4Decompressor.cs
│ ├── LZipDecompressor.cs
│ ├── NoChangeDecompressor.cs
│ ├── bzip2Decompressor.cs
│ ├── xzDecompressor.cs
│ └── zstdDecompressor.cs
├── Extractors
│ ├── CachedExtractor.cs
│ ├── ExtractorUsing7z.cs
│ ├── ExtractorUsing7zFM.cs
│ ├── ExtractorUsingSevenZipExtractor.cs
│ ├── ExtractorUsingSevenZipSharp.cs
│ ├── IExtractor.cs
│ ├── MultiExtractor.cs
│ └── SynchronisedExtractor.cs
├── GlobalSuppressions.cs
├── PartitionContainers
│ ├── ClonezillaImage.cs
│ ├── ImageFiles
│ │ ├── CompressedDriveImage.cs
│ │ ├── CompressedImage.cs
│ │ ├── CompressedPartitionImage.cs
│ │ ├── ImageFile.cs
│ │ ├── RawDriveImage.cs
│ │ ├── RawImage.cs
│ │ └── RawPartitionImage.cs
│ ├── PartcloneFile.cs
│ └── PartitionContainer.cs
├── Partitions
│ ├── ImageFilePartition.cs
│ ├── MountedContainer.cs
│ ├── MountedPartitionImage.cs
│ ├── PartclonePartition.cs
│ └── Partition.cs
├── Utility.cs
├── VFS
│ ├── IVFS.cs
│ ├── OnDemandVFS.cs
│ └── SevenZipBackedFileEntry.cs
└── libClonezilla.csproj
├── libCommon
├── Buffers.cs
├── ChildProcessTracker.cs
├── DesktopUtility.cs
├── Extensions.cs
├── GlobalSuppressions.cs
├── Lists
│ ├── IRangeComparer.cs
│ └── LazyList.cs
├── Logging
│ └── SuppressConsecutiveDuplicateFilter.cs
├── MainWindowFinder.cs
├── ProcessUtility.cs
├── Streams
│ ├── CachingStream.cs
│ ├── IReadSuggestor.cs
│ ├── IndependentStream.cs
│ ├── Multistream.cs
│ ├── PositionTrackerStream.cs
│ ├── Seekable
│ │ ├── SeekableStreamUsingNearestActioner.cs
│ │ └── SeekableStreamUsingRestarts.cs
│ ├── SiphonStream.cs
│ ├── Sparse
│ │ ├── ISparseAwareReader.cs
│ │ ├── ISparseAwareWriter.cs
│ │ ├── SparesAwareReadStream.cs
│ │ └── SparseAwareWriteStream.cs
│ ├── StreamUtility.cs
│ └── SubStream.cs
├── TempUtility.cs
├── Utility.cs
├── WindowHandleHelper.cs
└── libCommon.csproj
├── libDecompression
├── Lists
│ └── MappingComparer.cs
├── SeekableDecompressingStream.cs
├── Utilities
│ └── BoyerMoore.cs
└── libDecompression.csproj
├── libDokan
├── Extensions.cs
├── FindFilesPatternToRegex.cs
├── GlobalSuppressions.cs
├── Processes
│ └── ProcInfo.cs
├── Utility.cs
├── VFS
│ ├── DokanVFS.cs
│ ├── FileSystemEntry.cs
│ ├── Files
│ │ ├── FileEntry.cs
│ │ └── StreamBackedFileEntry.cs
│ └── Folders
│ │ ├── Folder.cs
│ │ ├── RestrictedFolderByPID.cs
│ │ ├── RootFolder.cs
│ │ └── UnlistedFolder.cs
└── libDokan.csproj
├── libGZip
├── GZipStreamSeekable.cs
├── ext
│ └── gztool
│ │ └── win-x86_64
│ │ └── gztool-Windows.x86_64.exe
└── libGZip.csproj
├── libPartclone
├── Cache
│ └── IPartcloneCache.cs
├── Extensions.cs
├── Lists
│ └── ContiguousRangeComparer.cs
├── Metadata
│ ├── FileSystemInfoV1.cs
│ ├── FileSystemInfoV2.cs
│ ├── ImageDescV1.cs
│ ├── ImageDescV2.cs
│ ├── ImageHeadV1.cs
│ ├── ImageHeadV2.cs
│ ├── ImageOptionsV1.cs
│ └── ImageOptionsV2.cs
├── PartcloneImageInfo.cs
├── PartcloneStream.cs
└── libPartclone.csproj
└── libTrainCompress
├── Compressors
├── Compressor.cs
├── gzCompressor.cs
├── xzCompressor.cs
└── zstdCompressor.cs
├── GlobalSuppressions.cs
├── Lists
└── CarriageComparer.cs
├── Streams
└── SubStream.cs
├── TrainCompressor.cs
├── TrainDecompressor.cs
└── libTrainCompress.csproj
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{cs,vb}]
2 |
3 | # IDE0060: Remove unused parameter
4 | dotnet_code_quality_unused_parameters = non_public:suggestion
5 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Fidel Perez-Smith
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # clonezilla-util
2 | Extract individual files from a Clonezilla archive, without extracting the full archive.
3 |
4 | ## Supported formats
5 | Clonezilla archives
6 | Partclone images
7 | Drive images (eg. sda.img)
8 | Partition images (eg. sda1.img)
9 | Compressed versions of the above (bzip2, GZip, LZ4, LZip, xz, Zstandard)
10 |
11 | ## Where to download
12 | Releases can be found over in the [releases](https://github.com/fiddyschmitt/clonezilla-util/releases) section.
13 |
14 |
15 |
16 | ## Mount a Clonezilla archive in Windows
17 |
18 | First, install [this version](https://github.com/dokan-dev/dokany/releases/tag/v2.1.0.1000) of the Dokan Driver. This is required to create a Virtual Drive.
19 |
20 | Now run the following command:
21 |
22 | `clonezilla-util.exe mount --input --mount L:\`
23 |
24 | A virtual drive is created, containing all the files in the Clonezilla archive:
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | ## Mount a Clonezilla archive as partition image files
37 |
38 | First, install [this version](https://github.com/dokan-dev/dokany/releases/tag/v2.1.0.1000) of the Dokan Driver. This is required to create a Virtual Drive.
39 |
40 | Now run the following command:
41 |
42 | `clonezilla-util.exe mount-as-image-files --input --mount L:\`
43 |
44 | A virtual drive is created, containing a file for each partition:
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | You can open them using 7-Zip, and extract individual files:
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | # Advanced features
64 |
65 | ## Mount raw images
66 |
67 | Run the following command:
68 |
69 | `clonezilla-util.exe mount --input "sda.img" --mount L:\`
70 |
71 |
72 |
73 | ## Extract partition images from Clonezilla archive (Dokan not required)
74 |
75 | Run the following command:
76 |
77 | `clonezilla-util.exe extract-partition-image --input --output `
78 |
79 | The program creates a file for each partition in the Clonezilla archive.
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | If the images are extracted to an NTFS drive, they are created as sparse. Meaning they only take up the necessary space:
91 |
92 |
93 |
94 |
95 |
96 |
97 | # Thanks
98 |
99 | A special thanks to Roberto for creating [gztool](https://github.com/circulosmeos/gztool). It allows the gz files in the Clonezilla archive to be read randomly, which gz doesn't natively support.
100 |
101 | Thanks to the [Dokan team](https://github.com/dokan-dev/dokan-dotnet). Dokan is what is used to create the virtual drive.
102 |
103 | Thanks to Igor for [7-Zip](https://sourceforge.net/projects/sevenzip/). His versatile tool inspects the contents of the image files, which can be in many formats (eg. NTFS, FAT, etc.)
104 |
105 | Thanks to Steven for [Clonezilla](https://github.com/stevenshiau/clonezilla), the backup tool used by millions.
106 |
--------------------------------------------------------------------------------
/clonezilla-util/CL/Verbs/BaseVerb.cs:
--------------------------------------------------------------------------------
1 | using CommandLine;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | namespace clonezilla_util.CL.Verbs
7 | {
8 | public class BaseVerb
9 | {
10 | [Option('i', "input", HelpText = "The folder containing the Clonezilla archive. Or, a partclone filename, or image filename.", Required = true, Min = 1)]
11 | public IEnumerable? InputPaths { get; set; }
12 |
13 | [Option("cache-folder", HelpText = "The folder to store cache files. Defaults to the 'cache' folder in the same location as the exe.", Required = false)]
14 | public string? CacheFolder { get; set; }
15 |
16 | [Option("temp-folder", HelpText = "The folder to store temporary files (if required)", Required = false)]
17 | public string? TempFolder { get; set; }
18 |
19 | [Option("process-trailing-nulls ", HelpText = "By default, the program skips trailing nulls to speed up processing of large files. Use this switch to force them to be processed.", Required = false)]
20 | public bool ProcessTrailingNulls { get; set; } = false;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/clonezilla-util/CL/Verbs/ExtractFiles.cs:
--------------------------------------------------------------------------------
1 | using CommandLine;
2 | using CommandLine.Text;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Text;
6 |
7 | namespace clonezilla_util.CL.Verbs
8 | {
9 | //[Verb("extract", HelpText = "Extract files (without using directory names)")]
10 | public class ExtractFiles : BaseVerb
11 | {
12 | public IEnumerable? Filenames;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/clonezilla-util/CL/Verbs/ExtractPartitionImage.cs:
--------------------------------------------------------------------------------
1 | using CommandLine;
2 | using CommandLine.Text;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Text;
6 |
7 | namespace clonezilla_util.CL.Verbs
8 | {
9 | [Verb("extract-partition-image", HelpText = "Extract the uncompressed, original copy of the partition.")]
10 | public class ExtractPartitionImage : BaseVerb
11 | {
12 | [Option('o', "output", HelpText = "The folder to extract the partition image to.", Required = true)]
13 | public string? OutputFolder { get; set; }
14 |
15 | [Option('p', "partitions", HelpText = "The partition(s) to extract from the Clonezilla archive. Eg. sda1. If not provided, all partitions will be extracted.", Required = false)]
16 | public IEnumerable PartitionsToExtract { get; set; } = [];
17 |
18 | [Option("no-sparse-output", HelpText = "By default, the program produces a sparse output file which is faster to generate and takes less disk space, while being identical to the original. Using this option, a full file is produced rather than a sparse file.", Required = false)]
19 | public bool NoSparseOutput { get; set; } = false;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/clonezilla-util/CL/Verbs/ListContents.cs:
--------------------------------------------------------------------------------
1 | using CommandLine;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | namespace clonezilla_util.CL.Verbs
7 | {
8 | [Verb("list", HelpText = "List files in the archive")]
9 | public class ListContents : BaseVerb
10 | {
11 | [Option('s', "separator", HelpText = "The seperator to use between results in the output", Default = "\n", Required = false)]
12 | public string OutputSeparator { get; set; } = Environment.NewLine;
13 |
14 | [Option("null-separator", HelpText = "Use a null character to seperate the results in the output", Default = false)]
15 | public bool UseNullSeparator { get; set; } = false;
16 |
17 | [Option('p', "partitions", HelpText = "The partition(s) to list contents of. Eg. sda1. If not provided, all partitions will be processed.", Required = false)]
18 | public IEnumerable PartitionsToInspect { get; set; } = [];
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/clonezilla-util/CL/Verbs/MountAsFiles.cs:
--------------------------------------------------------------------------------
1 | using CommandLine;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace clonezilla_util.CL.Verbs
9 | {
10 | [Verb("mount", HelpText = "Serve the file contents as using a Virtual File System. Requires Dokan to be installed. https://dokan-dev.github.io/")]
11 | public class MountAsFiles : BaseVerb
12 | {
13 | [Option('m', "mount", HelpText = "The drive to mount to, where the files will be presented. If not provided, a drive letter will automatically be chosen.", Required = false)]
14 | public string? MountPoint { get; set; }
15 |
16 | [Option('p', "partitions", HelpText = "The partition(s) to serve. Eg. sda1. If not provided, all partitions will be served.", Required = false)]
17 | public IEnumerable PartitionsToMount { get; set; } = [];
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/clonezilla-util/CL/Verbs/MountAsImageFiles.cs:
--------------------------------------------------------------------------------
1 | using CommandLine;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace clonezilla_util.CL.Verbs
9 | {
10 | [Verb("mount-as-image-files", HelpText = "Serve the original partition images using a Virtual File System. Requires Dokan to be installed. https://dokan-dev.github.io/")]
11 | public class MountAsImageFiles : BaseVerb
12 | {
13 | [Option('m', "mount", HelpText = "The drive to mount to, where the partition images will be presented. If not provided, a drive letter will automatically be chosen.", Required = true)]
14 | public string? MountPoint { get; set; }
15 |
16 | [Option('p', "partitions", HelpText = "The partition(s) to serve. Eg. sda1. If not provided, all partitions will be served.", Required = false)]
17 | public IEnumerable PartitionsToMount { get; set; } = [];
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/clonezilla-util/Extensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Linq;
5 | using System.IO;
6 | using System.Security.Cryptography;
7 |
8 | namespace clonezilla_util
9 | {
10 | public static class Extensions
11 | {
12 | public static bool IsEqualTo(this byte[] buffer1, byte[] otherBuffer)
13 | {
14 | var span1 = new ReadOnlySpan(buffer1, 0, buffer1.Length);
15 | var span2 = new ReadOnlySpan(otherBuffer, 0, otherBuffer.Length);
16 | var result = span1.SequenceEqual(span2);
17 | return result;
18 | }
19 |
20 | public static List> ChunkBy(this IEnumerable source, int chunkSize)
21 | {
22 | return source
23 | .Select((x, i) => new { Index = i, Value = x })
24 | .GroupBy(x => x.Index / chunkSize)
25 | .Select(x => x.Select(v => v.Value).ToList())
26 | .ToList();
27 | }
28 |
29 | public static IEnumerable Range(ulong start, ulong length)
30 | {
31 | var end = start + length;
32 | for (ulong i = start; i < end; i++)
33 | {
34 | yield return i;
35 | }
36 | }
37 |
38 | public static string ReplaceFirst(this string text, string search, string replace)
39 | {
40 | int pos = text.IndexOf(search);
41 | if (pos < 0)
42 | {
43 | return text;
44 | }
45 | return string.Concat(text.AsSpan(0, pos), replace, text.AsSpan(pos + search.Length));
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/clonezilla-util/clonezilla-util.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | clonezilla_util
7 | x64
8 | enable
9 |
10 |
11 |
12 | full
13 | true
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/clonezilla-util_tests/GlobalSuppressions.cs:
--------------------------------------------------------------------------------
1 | // This file is used by Code Analysis to maintain SuppressMessage
2 | // attributes that are applied to this project.
3 | // Project-level suppressions either have no target or are given
4 | // a specific target and scoped to a namespace, type, member, etc.
5 |
6 | using System.Diagnostics.CodeAnalysis;
7 |
8 | [assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "", Scope = "module")]
9 | [assembly: SuppressMessage("Interoperability", "SYSLIB1054:Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time", Justification = "", Scope = "module")]
10 | [assembly: SuppressMessage("Usage", "CA2211:Non-constant fields should not be visible", Justification = "", Scope = "module")]
11 |
--------------------------------------------------------------------------------
/clonezilla-util_tests/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | global using Microsoft.VisualStudio.TestTools.UnitTesting;
2 |
3 | [assembly: Parallelize(Workers = 4, Scope = ExecutionScope.ClassLevel)]
--------------------------------------------------------------------------------
/clonezilla-util_tests/ListContents/LargeClonezillaPartitions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace clonezilla_util_tests.ListContents
8 | {
9 | [DoNotParallelize]
10 | [TestClass]
11 | public class LargeClonezillaPartitions
12 | {
13 | [TestMethod]
14 | public void Bzip2()
15 | {
16 | TestUtility.ConfirmContainsStrings(
17 | Main.ExeUnderTest,
18 | """list --input "E:\clonezilla-util-test resources\clonezilla images\2022-07-16-22-img_pb-devops1_bzip2""",
19 | [
20 | @"2022-07-16-22-img_pb-devops1_bzip2\sda1\Recovery\Logs\Reload.xml",
21 | @"2022-07-16-22-img_pb-devops1_bzip2\sda2\Program Files\PostgreSQL\13\share\information_schema.sql",
22 | @"2022-07-16-22-img_pb-devops1_bzip2\sdb1\Kingsley\Prototype 1\Temp\Images\logo.jpg"
23 | ]);
24 | }
25 |
26 | [TestMethod]
27 | public void Gz()
28 | {
29 | TestUtility.ConfirmContainsStrings(
30 | Main.ExeUnderTest,
31 | """list --input "E:\clonezilla-util-test resources\clonezilla images\2022-07-17-16-img_pb-devops1_gz""",
32 | [
33 | @"2022-07-17-16-img_pb-devops1_gz\sda1\Recovery\Logs\Reload.xml",
34 | @"2022-07-17-16-img_pb-devops1_gz\sda2\Program Files\PostgreSQL\13\share\information_schema.sql",
35 | @"2022-07-17-16-img_pb-devops1_gz\sdb1\Kingsley\Prototype 1\Temp\Images\logo.jpg"
36 | ]);
37 | }
38 |
39 | [TestMethod]
40 | public void Xz()
41 | {
42 | TestUtility.ConfirmContainsStrings(
43 | Main.ExeUnderTest,
44 | """list --input "E:\clonezilla-util-test resources\clonezilla images\2022-07-17-12-img_pb-devops1_xz""",
45 | [
46 | @"2022-07-17-12-img_pb-devops1_xz\sda1\Recovery\Logs\Reload.xml",
47 | @"2022-07-17-12-img_pb-devops1_xz\sda2\Program Files\PostgreSQL\13\share\information_schema.sql",
48 | @"2022-07-17-12-img_pb-devops1_xz\sdb1\Kingsley\Prototype 1\Temp\Images\logo.jpg"
49 | ]);
50 | }
51 |
52 | [TestMethod]
53 | public void Zst()
54 | {
55 | TestUtility.ConfirmContainsStrings(
56 | Main.ExeUnderTest,
57 | """list --input "E:\clonezilla-util-test resources\clonezilla images\2022-07-16-22-img_pb-devops1_zst""",
58 | [
59 | @"2022-07-16-22-img_pb-devops1_zst\sda1\Recovery\Logs\Reload.xml",
60 | @"2022-07-16-22-img_pb-devops1_zst\sda2\Program Files\PostgreSQL\13\share\information_schema.sql",
61 | @"2022-07-16-22-img_pb-devops1_zst\sdb1\Kingsley\Prototype 1\Temp\Images\logo.jpg"
62 | ]);
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/clonezilla-util_tests/ListContents/Partclone.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace clonezilla_util_tests.ListContents
8 | {
9 | [DoNotParallelize]
10 | [TestClass]
11 | public class Partclone
12 | {
13 | [TestMethod]
14 | public void MixedPartcloneFormats()
15 | {
16 | TestUtility.ConfirmContainsStrings(
17 | Main.ExeUnderTest,
18 | """list --input "E:\clonezilla-util-test resources\clonezilla images\2020-11-15-00-img_small_drive_new_partclone (clonezilla-live-2.6.6-15)" "E:\clonezilla-util-test resources\clonezilla images\2020-05-23-23-img_small_drive_old_partclone (clonezilla-live-2.5.2-31)" """,
19 | [
20 | @"2020-05-23-23-img_small_drive_old_partclone (clonezilla-live-2.5.2-31)\sda1\sda1.txt",
21 | @"2020-05-23-23-img_small_drive_old_partclone (clonezilla-live-2.5.2-31)\sda2\sda2.txt",
22 |
23 | @"2020-11-15-00-img_small_drive_new_partclone (clonezilla-live-2.6.6-15)\sda1\sda1.txt",
24 | @"2020-11-15-00-img_small_drive_new_partclone (clonezilla-live-2.6.6-15)\sda2\sda2.txt"
25 | ]);
26 |
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/clonezilla-util_tests/ListContents/SmallClonezillaPartitions.cs:
--------------------------------------------------------------------------------
1 | namespace clonezilla_util_tests.ListContents
2 | {
3 | [TestClass]
4 | [DoNotParallelize]
5 | public class SmallClonezillaPartitions
6 | {
7 | [TestMethod]
8 | public void Bzip2()
9 | {
10 | TestUtility.ConfirmContainsStrings(
11 | Main.ExeUnderTest,
12 | """list --input "E:\clonezilla-util-test resources\clonezilla images\2022-07-16-22-img_pb-devops1_bzip2" -p sda1 sdb1""",
13 | [
14 | @"2022-07-16-22-img_pb-devops1_bzip2\sda1\Recovery\Logs\Reload.xml",
15 | @"2022-07-16-22-img_pb-devops1_bzip2\sdb1\Kingsley\Prototype 1\Temp\Images\logo.jpg"
16 | ]);
17 | }
18 |
19 | [TestMethod]
20 | public void gz()
21 | {
22 | TestUtility.ConfirmContainsStrings(
23 | Main.ExeUnderTest,
24 | """list --input "E:\clonezilla-util-test resources\clonezilla images\2022-07-17-16-img_pb-devops1_gz" -p sda1 sdb1""",
25 | [
26 | @"2022-07-17-16-img_pb-devops1_gz\sda1\Recovery\Logs\Reload.xml",
27 | @"2022-07-17-16-img_pb-devops1_gz\sdb1\Kingsley\Prototype 1\Temp\Images\logo.jpg"
28 | ]);
29 | }
30 |
31 | [TestMethod]
32 | public void xz()
33 | {
34 | TestUtility.ConfirmContainsStrings(
35 | Main.ExeUnderTest,
36 | """list --input "E:\clonezilla-util-test resources\clonezilla images\2022-07-17-12-img_pb-devops1_xz" -p sda1 sdb1""",
37 | [
38 | @"2022-07-17-12-img_pb-devops1_xz\sda1\Recovery\Logs\Reload.xml",
39 | @"2022-07-17-12-img_pb-devops1_xz\sdb1\Kingsley\Prototype 1\Temp\Images\logo.jpg"
40 | ]);
41 | }
42 |
43 | [TestMethod]
44 | public void zst()
45 | {
46 | TestUtility.ConfirmContainsStrings(
47 | Main.ExeUnderTest,
48 | """list --input "E:\clonezilla-util-test resources\clonezilla images\2022-07-16-22-img_pb-devops1_zst" -p sda1 sdb1""",
49 | [
50 | @"2022-07-16-22-img_pb-devops1_zst\sda1\Recovery\Logs\Reload.xml",
51 | @"2022-07-16-22-img_pb-devops1_zst\sdb1\Kingsley\Prototype 1\Temp\Images\logo.jpg"
52 | ]);
53 | }
54 | }
55 | }
--------------------------------------------------------------------------------
/clonezilla-util_tests/ListContents/SmallPartitionImages.cs:
--------------------------------------------------------------------------------
1 | namespace clonezilla_util_tests.ListContents
2 | {
3 | [TestClass]
4 | [DoNotParallelize]
5 | public class SmallPartitionImages
6 | {
7 | [TestMethod]
8 | public void Bzip2()
9 | {
10 | TestUtility.ConfirmContainsStrings(
11 | Main.ExeUnderTest,
12 | """list --input "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda1.img.bz2""",
13 | [
14 | @"2021-12-28_pb-devops1_sda1.img\partition0\Recovery\WindowsRE\ReAgent.xml",
15 | ]);
16 | }
17 |
18 | [TestMethod]
19 | public void gz()
20 | {
21 | TestUtility.ConfirmContainsStrings(
22 | Main.ExeUnderTest,
23 | """list --input "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda1.img.gz""",
24 | [
25 | @"2021-12-28_pb-devops1_sda1.img\partition0\Recovery\WindowsRE\ReAgent.xml",
26 | ]);
27 | }
28 |
29 | [TestMethod]
30 | public void raw()
31 | {
32 | TestUtility.ConfirmContainsStrings(
33 | Main.ExeUnderTest,
34 | """list --input "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda1.img""",
35 | [
36 | @"2021-12-28_pb-devops1_sda1\partition0\Recovery\WindowsRE\ReAgent.xml",
37 | ]);
38 | }
39 |
40 | [TestMethod]
41 | public void xz()
42 | {
43 | TestUtility.ConfirmContainsStrings(
44 | Main.ExeUnderTest,
45 | """list --input "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda1.img.xz""",
46 | [
47 | @"2021-12-28_pb-devops1_sda1.img\partition0\Recovery\WindowsRE\ReAgent.xml",
48 | ]);
49 | }
50 |
51 | [TestMethod]
52 | public void zst()
53 | {
54 | TestUtility.ConfirmContainsStrings(
55 | Main.ExeUnderTest,
56 | """list --input "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda1.img.zst""",
57 | [
58 | @"2021-12-28_pb-devops1_sda1.img\partition0\Recovery\WindowsRE\ReAgent.xml",
59 | ]);
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/clonezilla-util_tests/ListContents/TestUtility.cs:
--------------------------------------------------------------------------------
1 | using libCommon;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace clonezilla_util_tests.ListContents
10 | {
11 | public static class TestUtility
12 | {
13 | public static void ConfirmContainsStrings(string exeUnderTest, string args, IList expectedStrings)
14 | {
15 | var output = ProcessUtility.GetProgramOutput(exeUnderTest, args);
16 |
17 | expectedStrings
18 | .ToList()
19 | .ForEach(expectedString =>
20 | {
21 | var contains = output.Contains(expectedString);
22 | Assert.IsTrue(contains, $"Could not find: {expectedString}");
23 | });
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/clonezilla-util_tests/Main.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace clonezilla_util_tests
9 | {
10 | [TestClass]
11 | public static class Main
12 | {
13 | public const string ExeUnderTest = @"R:\Temp\clonezilla-util release\clonezilla-util.exe";
14 | public static bool RunLargeTests = true;
15 |
16 | [AssemblyInitialize]
17 | public static void AssemblyInit(TestContext context)
18 | {
19 | Process
20 | .GetProcesses()
21 | .Where(pr => pr.ProcessName == "clonezilla-util")
22 | .ToList()
23 | .ForEach(p =>
24 | {
25 | p.Kill();
26 | p.WaitForExit();
27 | });
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/clonezilla-util_tests/Mount/AsFiles/Ext4.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using static clonezilla_util_tests.Mount.TestUtility;
7 |
8 | namespace clonezilla_util_tests.Mount.AsFiles
9 | {
10 | [TestClass]
11 | [DoNotParallelize]
12 | public class Ext4
13 | {
14 | [TestMethod]
15 | public void ext4()
16 | {
17 | ConfirmFilesExist(
18 | Main.ExeUnderTest,
19 | """mount --input "E:\clonezilla-util-test resources\drive images\ext4\2022-08-16_ext4.img" --mount L:\ """,
20 | [
21 | new FileDetails(@"L:\hello.txt", "bad9425ff652b1bd52b49720abecf0ba"),
22 | new FileDetails(@"L:\config_files\test.xml", "81dfa8e288df74cebc654c582a3abebc"),
23 | ]);
24 | }
25 |
26 | [TestMethod]
27 | public void ext4_zst()
28 | {
29 | //Takes a long time because 7z.exe tries to scan across the entire 33 GB file, requiring the zstandard stream (even though only 1 MB) to be opened over and over by SeekableStreamUsingRestarts
30 |
31 | ConfirmFilesExist(
32 | Main.ExeUnderTest,
33 | """mount --input "E:\clonezilla-util-test resources\drive images\ext4\2022-08-16_ext4.img.zst" --mount L:\ """,
34 | [
35 | new FileDetails(@"L:\hello.txt", "bad9425ff652b1bd52b49720abecf0ba"),
36 | new FileDetails(@"L:\config_files\test.xml", "81dfa8e288df74cebc654c582a3abebc"),
37 | ]);
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/clonezilla-util_tests/Mount/AsFiles/LargeClonezillaImages.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using static clonezilla_util_tests.Mount.TestUtility;
7 |
8 | namespace clonezilla_util_tests.Mount.AsFiles
9 | {
10 | [TestClass]
11 | [DoNotParallelize]
12 | public class LargeClonezillaImages
13 | {
14 | [TestMethod]
15 | public void bzip2()
16 | {
17 | ConfirmFilesExist(
18 | Main.ExeUnderTest,
19 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2022-07-16-22-img_pb-devops1_bzip2" -m L:\""",
20 | [
21 | new FileDetails(@"L:\sda1\Recovery\Logs\Reload.xml", "f5a6df3c8f1ad69766afee3a25f7e376"),
22 | new FileDetails(@"L:\sda2\Program Files\PostgreSQL\13\share\information_schema.sql", "97a4aa50fa682b00457810d010ff5852"),
23 | new FileDetails(@"L:\sdb1\Kingsley\Prototype 1\Temp\Images\logo.jpg", "0217ff1926ec5f82e1a120676eff70c3"),
24 | ]);
25 | }
26 |
27 | [TestMethod]
28 | public void gz()
29 | {
30 | ConfirmFilesExist(
31 | Main.ExeUnderTest,
32 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2022-07-17-16-img_pb-devops1_gz" -m L:\""",
33 | [
34 | new FileDetails(@"L:\sda1\Recovery\Logs\Reload.xml", "f5a6df3c8f1ad69766afee3a25f7e376"),
35 | new FileDetails(@"L:\sda2\Program Files\PostgreSQL\13\share\information_schema.sql", "97a4aa50fa682b00457810d010ff5852"),
36 | new FileDetails(@"L:\sdb1\Kingsley\Prototype 1\Temp\Images\logo.jpg", "0217ff1926ec5f82e1a120676eff70c3"),
37 | ]);
38 | }
39 |
40 | [TestMethod]
41 | public void xz()
42 | {
43 | ConfirmFilesExist(
44 | Main.ExeUnderTest,
45 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2022-07-17-12-img_pb-devops1_xz" -m L:\""",
46 | [
47 | new FileDetails(@"L:\sda1\Recovery\Logs\Reload.xml", "f5a6df3c8f1ad69766afee3a25f7e376"),
48 | new FileDetails(@"L:\sda2\Program Files\PostgreSQL\13\share\information_schema.sql", "97a4aa50fa682b00457810d010ff5852"),
49 | new FileDetails(@"L:\sdb1\Kingsley\Prototype 1\Temp\Images\logo.jpg", "0217ff1926ec5f82e1a120676eff70c3"),
50 | ]);
51 | }
52 |
53 | [TestMethod]
54 | public void zst()
55 | {
56 | ConfirmFilesExist(
57 | Main.ExeUnderTest,
58 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2022-07-16-22-img_pb-devops1_zst" -m L:\""",
59 | [
60 | new FileDetails(@"L:\sda1\Recovery\Logs\Reload.xml", "f5a6df3c8f1ad69766afee3a25f7e376"),
61 | new FileDetails(@"L:\sda2\Program Files\PostgreSQL\13\share\information_schema.sql", "97a4aa50fa682b00457810d010ff5852"),
62 | new FileDetails(@"L:\sdb1\Kingsley\Prototype 1\Temp\Images\logo.jpg", "0217ff1926ec5f82e1a120676eff70c3"),
63 | ]);
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/clonezilla-util_tests/Mount/AsFiles/LargeDriveImages.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using static clonezilla_util_tests.Mount.TestUtility;
7 |
8 | namespace clonezilla_util_tests.Mount.AsFiles
9 | {
10 | [TestClass]
11 | [DoNotParallelize]
12 | public class LargeDriveImages
13 | {
14 | [TestMethod]
15 | public void bzip2()
16 | {
17 | if (!Main.RunLargeTests)
18 | {
19 | Assert.Inconclusive($"Not run. ({nameof(Main.RunLargeTests)} = False)");
20 | return;
21 | }
22 |
23 | ConfirmFilesExist(
24 | Main.ExeUnderTest,
25 | """mount --input "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda.img.bz2" -m L:\""",
26 | [
27 | new FileDetails(@"L:\partition0\Recovery\WindowsRE\ReAgent.xml", "464bd66c6443e55b791f16cb6bc28c2e"),
28 | new FileDetails(@"L:\partition1\Windows\INF\cpu.inf", "b724a9d3590bab87235fe1de985e748c"),
29 | ]);
30 | }
31 |
32 | [TestMethod]
33 | public void gz()
34 | {
35 | ConfirmFilesExist(
36 | Main.ExeUnderTest,
37 | """mount --input "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda.img.gz" -m L:\""",
38 | [
39 | new FileDetails(@"L:\partition0\Recovery\WindowsRE\ReAgent.xml", "464bd66c6443e55b791f16cb6bc28c2e"),
40 | new FileDetails(@"L:\partition1\Windows\INF\cpu.inf", "b724a9d3590bab87235fe1de985e748c"),
41 | ]);
42 | }
43 |
44 | [TestMethod]
45 | public void Raw()
46 | {
47 | ConfirmFilesExist(
48 | Main.ExeUnderTest,
49 | """mount --input "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda.img" -m L:\""",
50 | [
51 | new FileDetails(@"L:\partition0\Recovery\WindowsRE\ReAgent.xml", "464bd66c6443e55b791f16cb6bc28c2e"),
52 | new FileDetails(@"L:\partition1\Windows\INF\cpu.inf", "b724a9d3590bab87235fe1de985e748c"),
53 | ]);
54 | }
55 |
56 | [TestMethod]
57 | public void xz()
58 | {
59 | ConfirmFilesExist(
60 | Main.ExeUnderTest,
61 | """mount --input "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda.img.xz" -m L:\""",
62 | [
63 | new FileDetails(@"L:\partition0\Recovery\WindowsRE\ReAgent.xml", "464bd66c6443e55b791f16cb6bc28c2e"),
64 | new FileDetails(@"L:\partition1\Windows\INF\cpu.inf", "b724a9d3590bab87235fe1de985e748c"),
65 | ]);
66 | }
67 |
68 | [TestMethod]
69 | public void zst()
70 | {
71 | ConfirmFilesExist(
72 | Main.ExeUnderTest,
73 | """mount --input "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda.img.zst" -m L:\""",
74 | [
75 | new FileDetails(@"L:\partition0\Recovery\WindowsRE\ReAgent.xml", "464bd66c6443e55b791f16cb6bc28c2e"),
76 | new FileDetails(@"L:\partition1\Windows\INF\cpu.inf", "b724a9d3590bab87235fe1de985e748c"),
77 | ]);
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/clonezilla-util_tests/Mount/AsFiles/LuksClonezillaImages.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using static clonezilla_util_tests.Mount.TestUtility;
7 |
8 | namespace clonezilla_util_tests.Mount.AsFiles
9 | {
10 | [TestClass]
11 | [DoNotParallelize]
12 | public class LuksClonezillaImages
13 | {
14 | [TestMethod]
15 | public void luks_ntfs_20GB()
16 | {
17 | //20GB ntfs -> luks -> partclone -> zst
18 | ConfirmFilesExist(
19 | Main.ExeUnderTest,
20 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2022-08-16-15-img_luks_test_20GB_ntfs" --mount L:\ """,
21 | [
22 | new FileDetails(@"L:\howdy.txt", "f521b93b9a4f632a537163f599ded439"),
23 | new FileDetails(@"L:\second_file.txt", "b1946ac92492d2347c6235b4d2611184"),
24 | ]);
25 | }
26 |
27 | [TestMethod]
28 | public void luks_ntfs_6GB()
29 | {
30 | //6GB ntfs -> luks -> partclone -> zst
31 | ConfirmFilesExist(
32 | Main.ExeUnderTest,
33 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2022-08-16-20-img_luks_test_6GB_ext4_zst" --mount L:\ """,
34 | [
35 | new FileDetails(@"L:\another_file.txt", "42690a6bf443aa07821ccc51e58e950c"),
36 | new FileDetails(@"L:\hello.txt", "ce55c98ac24d4c7764877fa58ab441ef"),
37 | ]);
38 | }
39 |
40 | [TestMethod]
41 | public void luks_ext4_500GB_gz()
42 | {
43 | //500 GB ext4 -> luks -> partclone -> gz
44 | ConfirmFilesExist(
45 | Main.ExeUnderTest,
46 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2022-08-16-10-img_luks_test_500GB_ext4_gz" --mount L:\ """,
47 | [
48 | new FileDetails(@"L:\file1.txt", "bad9425ff652b1bd52b49720abecf0ba"),
49 | new FileDetails(@"L:\file2.txt", "0f007fde795734c616b558bc6692c06a"),
50 | ]);
51 | }
52 |
53 | [TestMethod]
54 | public void luks_ext4_500GB_zst()
55 | {
56 | //500GB ext4 -> luks -> partclone -> zst
57 | ConfirmFilesExist(
58 | Main.ExeUnderTest,
59 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2022-08-16-09-img_luks_test_500GB_ext4_zst" --mount L:\ """,
60 | [
61 | new FileDetails(@"L:\file1.txt", "bad9425ff652b1bd52b49720abecf0ba"),
62 | new FileDetails(@"L:\file2.txt", "0f007fde795734c616b558bc6692c06a"),
63 | ]);
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/clonezilla-util_tests/Mount/AsFiles/LuksParcloneImages.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using static clonezilla_util_tests.Mount.TestUtility;
7 |
8 | namespace clonezilla_util_tests.Mount.AsFiles
9 | {
10 | [TestClass]
11 | [DoNotParallelize]
12 | public class LuksParcloneImages
13 | {
14 | [TestMethod]
15 | public void luks_ntfs_20GB()
16 | {
17 | //20GB ntfs -> luks -> partclone -> zst
18 | ConfirmFilesExist(
19 | Main.ExeUnderTest,
20 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2022-08-16-15-img_luks_test_20GB_ntfs\ocs_luks_esc.ntfs-ptcl-img.zst" --mount L:\ """,
21 | [
22 | new FileDetails(@"L:\howdy.txt", "f521b93b9a4f632a537163f599ded439"),
23 | new FileDetails(@"L:\second_file.txt", "b1946ac92492d2347c6235b4d2611184"),
24 | ]);
25 | }
26 |
27 | [TestMethod]
28 | public void luks_ntfs_6GB()
29 | {
30 | //6GB ext4 -> luks -> partclone -> zst
31 | ConfirmFilesExist(
32 | Main.ExeUnderTest,
33 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2022-08-16-20-img_luks_test_6GB_ext4_zst\ocs_luks_0Yy.ext4-ptcl-img.zst" --mount L:\ """,
34 | [
35 | new FileDetails(@"L:\another_file.txt", "42690a6bf443aa07821ccc51e58e950c"),
36 | new FileDetails(@"L:\hello.txt", "ce55c98ac24d4c7764877fa58ab441ef"),
37 | ]);
38 | }
39 |
40 | [TestMethod]
41 | public void luks_ext4_500GB_gz()
42 | {
43 | //500 GB ext4 -> luks->partclone->gz
44 | ConfirmFilesExist(
45 | Main.ExeUnderTest,
46 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2022-08-16-10-img_luks_test_500GB_ext4_gz\ocs_luks_OLi.ext4-ptcl-img.gz" --mount L:\ """,
47 | [
48 | new FileDetails(@"L:\file1.txt", "bad9425ff652b1bd52b49720abecf0ba"),
49 | new FileDetails(@"L:\file2.txt", "0f007fde795734c616b558bc6692c06a"),
50 | ]);
51 | }
52 |
53 | [TestMethod]
54 | public void luks_ext4_500GB_zst()
55 | {
56 | //500GB ext4 -> luks->partclone->zst
57 | ConfirmFilesExist(
58 | Main.ExeUnderTest,
59 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2022-08-16-09-img_luks_test_500GB_ext4_zst\ocs_luks_pDw.ext4-ptcl-img.zst" --mount L:\ """,
60 | [
61 | new FileDetails(@"L:\file1.txt", "bad9425ff652b1bd52b49720abecf0ba"),
62 | new FileDetails(@"L:\file2.txt", "0f007fde795734c616b558bc6692c06a"),
63 | ]);
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/clonezilla-util_tests/Mount/AsFiles/Misc.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using static clonezilla_util_tests.Mount.TestUtility;
7 |
8 | namespace clonezilla_util_tests.Mount.AsFiles
9 | {
10 | [TestClass]
11 | [DoNotParallelize]
12 | public class Misc
13 | {
14 | [TestMethod]
15 | public void MultipleContainers_MultiplePartitions()
16 | {
17 | ConfirmFilesExist(
18 | Main.ExeUnderTest,
19 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2022-07-17-16-img_pb-devops1_gz" "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda1.img.zst" -p sda1 sdb1 partition0 --mount L:\ """,
20 | [
21 | new FileDetails(@"L:\2022-07-17-16-img_pb-devops1_gz\sda1\Recovery\Logs\Reload.xml", "f5a6df3c8f1ad69766afee3a25f7e376"),
22 | new FileDetails(@"L:\2022-07-17-16-img_pb-devops1_gz\sdb1\Kingsley\Prototype 1\Temp\Images\logo.jpg", "0217ff1926ec5f82e1a120676eff70c3"),
23 |
24 | new FileDetails(@"L:\2021-12-28_pb-devops1_sda1.img\Recovery\WindowsRE\ReAgent.xml", "464bd66c6443e55b791f16cb6bc28c2e")
25 | ]);
26 | }
27 |
28 | [TestMethod]
29 | public void LastestClonezilla_2022_06_29()
30 | {
31 | ConfirmFilesExist(
32 | Main.ExeUnderTest,
33 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2022-06-27-20-img_small_drive" -m L:\ """,
34 | [
35 | new FileDetails(@"L:\sda1\sda1.txt", "c3f38733914d360530455ba3b4073868"),
36 | ]);
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/clonezilla-util_tests/Mount/AsFiles/Partclone.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using static clonezilla_util_tests.Mount.TestUtility;
7 |
8 | namespace clonezilla_util_tests.Mount.AsFiles
9 | {
10 | [TestClass]
11 | [DoNotParallelize]
12 | public class Partclone
13 | {
14 | [TestMethod]
15 | public void PartcloneImage()
16 | {
17 | ConfirmFilesExist(
18 | Main.ExeUnderTest,
19 | """mount --input "E:\clonezilla-util-test resources\partclone images\2021-12-29 - partclone file\sdb1.ntfs-ptcl-img" -m L:\ """,
20 | [
21 | new FileDetails(@"L:\Kingsley\Prototype 1\Temp\Images\logo.jpg", "0217ff1926ec5f82e1a120676eff70c3"),
22 | ]);
23 | }
24 |
25 | [TestMethod]
26 | public void gz()
27 | {
28 | ConfirmFilesExist(
29 | Main.ExeUnderTest,
30 | """mount --input "E:\clonezilla-util-test resources\partclone images\2022-07-17 - partclone file\sda1.ntfs-ptcl-img.gz" -m L:\ """,
31 | [
32 | new FileDetails(@"L:\Recovery\Logs\Reload.xml", "f5a6df3c8f1ad69766afee3a25f7e376"),
33 | ]);
34 | }
35 |
36 | [TestMethod]
37 | public void dd()
38 | {
39 | //clonezilla (partclone.dd + gz)
40 | ConfirmFilesExist(
41 | Main.ExeUnderTest,
42 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2023-11-17-00-img_PB-DEVOPS1_using_dd" -m L:\""",
43 | [
44 | new FileDetails(@"L:\sda1\Recovery\Logs\Reload.xml", "f5a6df3c8f1ad69766afee3a25f7e376"),
45 | new FileDetails(@"L:\sda2\Program Files\PostgreSQL\13\share\information_schema.sql", "97a4aa50fa682b00457810d010ff5852"),
46 | new FileDetails(@"L:\sdb1\Kingsley\Prototype 1\Temp\Images\logo.jpg", "0217ff1926ec5f82e1a120676eff70c3"),
47 | ]);
48 | }
49 |
50 | [TestMethod]
51 | public void MixedPartcloneFormats()
52 | {
53 | //two different clonezilla formats
54 | ConfirmFilesExist(
55 | Main.ExeUnderTest,
56 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2020-11-15-00-img_small_drive_new_partclone (clonezilla-live-2.6.6-15)" "E:\clonezilla-util-test resources\clonezilla images\2020-05-23-23-img_small_drive_old_partclone (clonezilla-live-2.5.2-31)" -m L:\ """,
57 | [
58 | new FileDetails(@"L:\2020-05-23-23-img_small_drive_old_partclone (clonezilla-live-2.5.2-31)\sda1\sda1.txt", "c3f38733914d360530455ba3b4073868"),
59 | new FileDetails(@"L:\2020-05-23-23-img_small_drive_old_partclone (clonezilla-live-2.5.2-31)\sda2\sda2.txt", "b80328235f5d991c6dc8982e1d1876bc"),
60 |
61 | new FileDetails(@"L:\2020-11-15-00-img_small_drive_new_partclone (clonezilla-live-2.6.6-15)\sda1\sda1.txt", "c3f38733914d360530455ba3b4073868"),
62 | new FileDetails(@"L:\2020-11-15-00-img_small_drive_new_partclone (clonezilla-live-2.6.6-15)\sda2\sda2.txt", "b80328235f5d991c6dc8982e1d1876bc"),
63 | ]);
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/clonezilla-util_tests/Mount/AsFiles/SmallPartitionImages.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using static clonezilla_util_tests.Mount.TestUtility;
7 |
8 | namespace clonezilla_util_tests.Mount.AsFiles
9 | {
10 | [TestClass]
11 | [DoNotParallelize]
12 | public class SmallPartitionImages
13 | {
14 | [TestMethod]
15 | public void Bzip2()
16 | {
17 | ConfirmFilesExist(
18 | Main.ExeUnderTest,
19 | """mount --input "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda1.img.bz2" -m L:\""",
20 | [
21 | new FileDetails(@"L:\Recovery\WindowsRE\ReAgent.xml", "464bd66c6443e55b791f16cb6bc28c2e"),
22 | ]);
23 | }
24 |
25 | [TestMethod]
26 | public void gz()
27 | {
28 | ConfirmFilesExist(
29 | Main.ExeUnderTest,
30 | """mount --input "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda1.img.gz" -m L:\""",
31 | [
32 | new FileDetails(@"L:\Recovery\WindowsRE\ReAgent.xml", "464bd66c6443e55b791f16cb6bc28c2e")
33 | ]);
34 |
35 | }
36 |
37 | [TestMethod]
38 | public void Raw()
39 | {
40 | ConfirmFilesExist(
41 | Main.ExeUnderTest,
42 | """mount --input "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda1.img" -m L:\""",
43 | [
44 | new FileDetails(@"L:\Recovery\WindowsRE\ReAgent.xml", "464bd66c6443e55b791f16cb6bc28c2e")
45 | ]);
46 | }
47 |
48 | [TestMethod]
49 | public void xz()
50 | {
51 | ConfirmFilesExist(
52 | Main.ExeUnderTest,
53 | """mount --input "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda1.img.xz" -m L:\""",
54 | [
55 | new FileDetails(@"L:\Recovery\WindowsRE\ReAgent.xml", "464bd66c6443e55b791f16cb6bc28c2e")
56 | ]);
57 | }
58 |
59 | [TestMethod]
60 | public void zst()
61 | {
62 | ConfirmFilesExist(
63 | Main.ExeUnderTest,
64 | """mount --input "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda1.img.zst" -m L:\""",
65 | [
66 | new FileDetails(@"L:\Recovery\WindowsRE\ReAgent.xml", "464bd66c6443e55b791f16cb6bc28c2e")
67 | ]);
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/clonezilla-util_tests/Mount/AsFiles/UbuntuFileSystems.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using static clonezilla_util_tests.Mount.TestUtility;
7 |
8 | namespace clonezilla_util_tests.Mount.AsFiles
9 | {
10 | [TestClass]
11 | [DoNotParallelize]
12 | public class UbuntuFileSystems
13 | {
14 | [TestMethod]
15 | public void ext4()
16 | {
17 | //default Ubuntu file system (ext4)
18 | ConfirmFilesExist(
19 | Main.ExeUnderTest,
20 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2022-09-13-12-img_ubuntu_22.04_default_filesystem" --mount L:\ """,
21 | [
22 | new FileDetails(@"L:\sda2\EFI\ubuntu\grub.cfg", "51f2a19ab5455fc3b7e2e2a0af00b9c0"),
23 | new FileDetails(@"L:\sda3\etc\fstab", "ea6ab8635f91425f7b1566a2d2f9675b"),
24 | ]);
25 | }
26 |
27 | [TestMethod]
28 | public void ext4_lvm()
29 | {
30 | //Ubuntu installed using LVM file system
31 | ConfirmFilesExist(
32 | Main.ExeUnderTest,
33 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2022-09-13-12-img_ubuntu_22.04_lvm_filesystem" --mount L:\ """,
34 | [
35 | new FileDetails(@"L:\sda2\EFI\ubuntu\grub.cfg", "3d20729047129a9315aad73359090eab"),
36 | new FileDetails(@"L:\vgubuntu-root\etc\fstab", "8aa06a520842b37790b91ebe67ccba06"),
37 | ]);
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/clonezilla-util_tests/Mount/AsImageFiles/ImageFileTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using static clonezilla_util_tests.Mount.TestUtility;
7 |
8 | namespace clonezilla_util_tests.Mount.AsImageFiles
9 | {
10 | [TestClass]
11 | [DoNotParallelize]
12 | public class ImageFileTests
13 | {
14 | [TestMethod]
15 | public void Gz()
16 | {
17 | TestUtility.ConfirmFilesExist(
18 | Main.ExeUnderTest,
19 | """mount-as-image-files --input "E:\clonezilla-util-test resources\clonezilla images\2022-07-17-16-img_pb-devops1_gz" -p sda1 -m L:\ """,
20 | [
21 | new FileDetails(@"L:\sda1.img", "d9a64d07801cb9f5878a72da4c5d53fd"),
22 | ]);
23 | }
24 |
25 | [TestMethod]
26 | public void Partclone()
27 | {
28 | //partclone image
29 | TestUtility.ConfirmFilesExist(
30 | Main.ExeUnderTest,
31 | """mount-as-image-files --input "E:\clonezilla-util-test resources\partclone images\2021-12-29 - partclone file\sdb1.ntfs-ptcl-img" -m L:\ """,
32 | [
33 | new FileDetails(@"L:\sdb1.ntfs-ptcl-img.img", "82b4fbf40812fe3354c855ee10fdc839", 1024 * 1024 * 1024),
34 | ]);
35 | }
36 |
37 | [TestMethod]
38 | public void LuksNtfs6GB()
39 | {
40 | //6GB ext4 -> luks -> partclone -> zst
41 | TestUtility.ConfirmFilesExist(
42 | Main.ExeUnderTest,
43 | """mount-as-image-files --input "E:\clonezilla-util-test resources\clonezilla images\2022-08-16-20-img_luks_test_6GB_ext4_zst\ocs_luks_0Yy.ext4-ptcl-img.zst" --mount L:\ """,
44 | [
45 | new FileDetails(@"L:\partition0.img", "a3217b31c6f2e5270174a9c536a90242"),
46 | ]);
47 | }
48 |
49 | [TestMethod]
50 | public void Zst()
51 | {
52 | TestUtility.ConfirmFilesExist(
53 | Main.ExeUnderTest,
54 | """mount-as-image-files --input "E:\clonezilla-util-test resources\clonezilla images\2022-07-16-22-img_pb-devops1_zst" -p sda1 -m L:\ """,
55 | [
56 | new FileDetails(@"L:\sda1.img", "d9a64d07801cb9f5878a72da4c5d53fd"),
57 | ]);
58 | }
59 |
60 | [TestMethod]
61 | public void UncompressedPartitionImage_and_gzClonezillaImage()
62 | {
63 | //uncompressed partition image and gz clonezilla image
64 | TestUtility.ConfirmFilesExist(
65 | Main.ExeUnderTest,
66 | """mount-as-image-files --input "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda1.img" "E:\clonezilla-util-test resources\clonezilla images\2022-07-17-16-img_pb-devops1_gz" -p sda1 partition0 -m L:\ """,
67 | [
68 | new FileDetails(@"L:\2021-12-28_pb-devops1_sda1\partition0.img", "d4559685d050cb9682369aad7fa0ba24"),
69 | new FileDetails(@"L:\2022-07-17-16-img_pb-devops1_gz\sda1.img", "d9a64d07801cb9f5878a72da4c5d53fd"),
70 | ]);
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/clonezilla-util_tests/Sparse/SparseTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Diagnostics;
5 | using System.Linq;
6 | using System.Runtime.InteropServices;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | namespace clonezilla_util_tests.Sparse
11 | {
12 | [TestClass]
13 | public class SparseTests
14 | {
15 | [TestMethod]
16 | public void ExtractAndSparsifyFile()
17 | {
18 | var outputFolder = Directory.CreateTempSubdirectory().FullName;
19 |
20 | var args = @$"extract-partition-image --input ""E:\clonezilla-util-test resources\clonezilla images\2022-07-17-16-img_pb-devops1_gz"" --output ""{outputFolder}"" -p sda2";
21 |
22 | var psi = new ProcessStartInfo(Main.ExeUnderTest, args)
23 | {
24 | UseShellExecute = false,
25 | CreateNoWindow = true
26 | };
27 | var process = Process.Start(psi);
28 | process?.WaitForExit();
29 |
30 | var extractedFilename = Directory.GetFiles(outputFolder).First();
31 | var fileSize = new FileInfo(extractedFilename).Length;
32 | var sizeOnDisk = GetFileSizeOnDisk(extractedFilename);
33 | Directory.Delete(outputFolder, true);
34 |
35 | var success = sizeOnDisk / (double)fileSize < 0.5;
36 | Assert.IsTrue(success, "Size On Disk is not smaller than file size");
37 | }
38 |
39 | public static long GetFileSizeOnDisk(string file)
40 | {
41 | var info = new FileInfo(file);
42 | if (info == null) return -1;
43 | if (info.Directory == null) return -1;
44 | var result = GetDiskFreeSpaceW(info.Directory.Root.FullName, out uint sectorsPerCluster, out uint bytesPerSector, out _, out _);
45 | if (result == 0) throw new Win32Exception();
46 | uint clusterSize = sectorsPerCluster * bytesPerSector;
47 | uint losize = GetCompressedFileSizeW(file, out uint hosize);
48 | long size;
49 | size = (long)hosize << 32 | losize;
50 | return ((size + clusterSize - 1) / clusterSize) * clusterSize;
51 | }
52 |
53 | [DllImport("kernel32.dll")]
54 | static extern uint GetCompressedFileSizeW([In, MarshalAs(UnmanagedType.LPWStr)] string lpFileName,
55 | [Out, MarshalAs(UnmanagedType.U4)] out uint lpFileSizeHigh);
56 |
57 | [DllImport("kernel32.dll", SetLastError = true, PreserveSig = true)]
58 | static extern int GetDiskFreeSpaceW([In, MarshalAs(UnmanagedType.LPWStr)] string lpRootPathName,
59 | out uint lpSectorsPerCluster, out uint lpBytesPerSector, out uint lpNumberOfFreeClusters,
60 | out uint lpTotalNumberOfClusters);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/clonezilla-util_tests/Train/TrainTests.cs:
--------------------------------------------------------------------------------
1 | using libTrainCompress;
2 | using libTrainCompress.Compressors;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using libCommon;
9 | using libCommon.Streams;
10 | using libCommon.Streams.Sparse;
11 | using System.Diagnostics;
12 |
13 | namespace clonezilla_util_tests.Train
14 | {
15 | [TestClass]
16 | public class TrainTests
17 | {
18 | [TestMethod]
19 | public void Zst()
20 | {
21 | TestTrain(
22 | @"E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda1.img",
23 | [new zstdCompressor()]
24 | );
25 | }
26 |
27 | [TestMethod]
28 | public void Gz()
29 | {
30 | TestTrain(
31 | @"E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda1.img",
32 | [new gzCompressor()]
33 | );
34 | }
35 |
36 | [TestMethod]
37 | public void Xz()
38 | {
39 | TestTrain(
40 | @"E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda1.img",
41 | [new xzCompressor()]
42 | );
43 | }
44 |
45 | public static void TestTrain(string inputFilename, IList compressors)
46 | {
47 | var originalFileStream = File.OpenRead(inputFilename);
48 |
49 | var compressedStream = new MemoryStream();
50 | using (var trainCompressor = new TrainCompressor(compressedStream, compressors, 10 * 1024 * 1024))
51 | {
52 | originalFileStream.CopyTo(trainCompressor, 50 * 1024 * 1024, progress =>
53 | {
54 | Debug.WriteLine($"Compressing {progress.Read.BytesToString()}");
55 | });
56 | }
57 |
58 |
59 | var uncompressedOutputStream = new MemoryStream();
60 | compressedStream.Seek(0, SeekOrigin.Begin);
61 | using (var trainDecompressor = new TrainDecompressor(compressedStream, compressors))
62 | {
63 | trainDecompressor.CopyTo(uncompressedOutputStream, 50 * 1024 * 1024, progress =>
64 | {
65 | Debug.WriteLine($"Decompressing {progress.Read.BytesToString()}");
66 | });
67 |
68 | uncompressedOutputStream.Seek(0, SeekOrigin.Begin);
69 | }
70 |
71 |
72 | var originalMd5 = Utility.CalculateMD5(originalFileStream);
73 | var outputMd5 = Utility.CalculateMD5(uncompressedOutputStream);
74 |
75 | var success = originalMd5.Equals(outputMd5);
76 | Assert.IsTrue(success, "MD5 hashes do not match");
77 |
78 |
79 | //test random seeking
80 | uncompressedOutputStream = new MemoryStream();
81 | compressedStream.Seek(0, SeekOrigin.Begin);
82 | using (var trainDecompressor = new TrainDecompressor(compressedStream, compressors))
83 | {
84 | Utilities.Utility.TestSeeking(trainDecompressor, uncompressedOutputStream);
85 |
86 | outputMd5 = Utility.CalculateMD5(uncompressedOutputStream);
87 |
88 | success = originalMd5.Equals(outputMd5);
89 | Assert.IsTrue(success, "MD5 hashes do not match after random seeking");
90 | }
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/clonezilla-util_tests/clonezilla-util_tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | clonezilla_util_tests
6 | enable
7 | enable
8 |
9 | false
10 | true
11 | x64
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | all
20 | runtime; build; native; contentfiles; analyzers; buildtransitive
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/lib7Zip/ArchiveEntry.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace lib7Zip
8 | {
9 | public class ArchiveEntry
10 | {
11 | public string Path;
12 |
13 | public ArchiveEntry(string path)
14 | {
15 | Path = path;
16 | }
17 |
18 | public bool IsFolder;
19 | public long Size;
20 | public DateTime Modified;
21 | public DateTime Created;
22 | public DateTime Accessed;
23 | public long? Offset;
24 |
25 | public override string ToString()
26 | {
27 | return Path;
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/lib7Zip/GlobalSuppressions.cs:
--------------------------------------------------------------------------------
1 | // This file is used by Code Analysis to maintain SuppressMessage
2 | // attributes that are applied to this project.
3 | // Project-level suppressions either have no target or are given
4 | // a specific target and scoped to a namespace, type, member, etc.
5 |
6 | using System.Diagnostics.CodeAnalysis;
7 |
8 | [assembly: SuppressMessage("Interoperability", "SYSLIB1054:Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time", Justification = "", Scope = "module")]
9 | [assembly: SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "", Scope = "module")]
10 | [assembly: SuppressMessage("Reliability", "CA2020:Prevent behavioral change", Justification = "", Scope = "module")]
11 |
--------------------------------------------------------------------------------
/lib7Zip/SevenZipExtractorUsingSevenZipExtractor.cs:
--------------------------------------------------------------------------------
1 | using Serilog;
2 | using SevenZip;
3 | using SevenZipExtractor;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | namespace lib7Zip
11 | {
12 | public class SevenZipExtractorUsingSevenZipExtractor
13 | {
14 | readonly ArchiveFile archiveFile;
15 |
16 | public SevenZipExtractorUsingSevenZipExtractor(string archiveFilename)
17 | {
18 | archiveFile = new ArchiveFile(archiveFilename);
19 | }
20 |
21 | public SevenZipExtractorUsingSevenZipExtractor(Stream stream)
22 | {
23 | archiveFile = new ArchiveFile(stream);
24 | }
25 |
26 | public void ExtractFile(string archiveEntryPath, Stream stream)
27 | {
28 | var entry = archiveFile
29 | .Entries
30 | .FirstOrDefault(entry => entry.FileName.Equals(archiveEntryPath, StringComparison.OrdinalIgnoreCase));
31 |
32 | entry?.Extract(stream);
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/lib7Zip/UI/WinAPI.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Runtime.InteropServices;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace lib7Zip.UI
9 | {
10 | static class WinAPI
11 | {
12 |
13 | public enum ListViewMessages
14 | {
15 | LVM_GETITEMTEXT = 0x104B
16 | }
17 |
18 | public enum ListViewItemFilters : uint
19 | {
20 | LVIF_TEXT = 0x0001,
21 | }
22 |
23 | public const uint MAX_LVMSTRING = 255;
24 |
25 | [StructLayoutAttribute(LayoutKind.Sequential)]
26 | public struct LVITEM
27 | {
28 | public uint mask;
29 | public int iItem;
30 | public int iSubItem;
31 | public uint state;
32 | public uint stateMask;
33 | public IntPtr pszText;
34 | public int cchTextMax;
35 | public int iImage;
36 | public IntPtr lParam;
37 | }
38 |
39 | [DllImport("user32.dll")]
40 | public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, IntPtr lParam);
41 |
42 | [DllImport("user32.dll")]
43 | public static extern uint GetWindowThreadProcessId(IntPtr hWnd, ref uint lpdwProcessId);
44 |
45 |
46 | [Flags]
47 | public enum ProcessAccessFlags : uint
48 | {
49 | VirtualMemoryOperation = 0x0008,
50 | VirtualMemoryRead = 0x0010,
51 | VirtualMemoryWrite = 0x0020,
52 | }
53 |
54 | [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
55 | public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, AllocationType flAllocationType, MemoryProtection flProtect);
56 |
57 | [Flags]
58 | public enum AllocationType
59 | {
60 | Commit = 0x1000,
61 | Release = 0x8000,
62 | }
63 |
64 | [Flags]
65 | public enum MemoryProtection
66 | {
67 | ReadWrite = 0x0004,
68 | }
69 |
70 | [DllImport("kernel32.dll")]
71 | public static extern IntPtr OpenProcess(ProcessAccessFlags processAccess, bool bInheritHandle, uint processId);
72 |
73 | [DllImport("kernel32.dll")]
74 | public static extern bool CloseHandle(IntPtr hHandle);
75 |
76 | [DllImport("kernel32.dll")]
77 | public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer, uint nSize, out int lpNumberOfBytesWritten);
78 |
79 | [DllImport("kernel32.dll")]
80 | public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] buffer, int dwSize, out IntPtr lpNumberOfBytesRead);
81 |
82 | [DllImport("kernel32.dll")]
83 | public static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, int dwSize, AllocationType dwFreeType);
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/lib7Zip/ext/7-Zip/win-x64/7z.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fiddyschmitt/clonezilla-util/bf65c0522e3035b4b201d065200c211ae734e563/lib7Zip/ext/7-Zip/win-x64/7z.dll
--------------------------------------------------------------------------------
/lib7Zip/ext/7-Zip/win-x64/7z.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fiddyschmitt/clonezilla-util/bf65c0522e3035b4b201d065200c211ae734e563/lib7Zip/ext/7-Zip/win-x64/7z.exe
--------------------------------------------------------------------------------
/lib7Zip/ext/7-Zip/win-x64/7zFM.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fiddyschmitt/clonezilla-util/bf65c0522e3035b4b201d065200c211ae734e563/lib7Zip/ext/7-Zip/win-x64/7zFM.exe
--------------------------------------------------------------------------------
/lib7Zip/ext/7-Zip/win-x86/7z.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fiddyschmitt/clonezilla-util/bf65c0522e3035b4b201d065200c211ae734e563/lib7Zip/ext/7-Zip/win-x86/7z.dll
--------------------------------------------------------------------------------
/lib7Zip/ext/7-Zip/win-x86/7z.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fiddyschmitt/clonezilla-util/bf65c0522e3035b4b201d065200c211ae734e563/lib7Zip/ext/7-Zip/win-x86/7z.exe
--------------------------------------------------------------------------------
/lib7Zip/ext/7-Zip/win-x86/7zFM.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fiddyschmitt/clonezilla-util/bf65c0522e3035b4b201d065200c211ae734e563/lib7Zip/ext/7-Zip/win-x86/7zFM.exe
--------------------------------------------------------------------------------
/lib7Zip/lib7Zip.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 | AnyCPU
8 | true
9 |
10 |
11 |
12 |
13 |
14 | PreserveNewest
15 |
16 |
17 | PreserveNewest
18 |
19 |
20 | PreserveNewest
21 |
22 |
23 | PreserveNewest
24 |
25 |
26 | PreserveNewest
27 |
28 |
29 | PreserveNewest
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/libBzip2/BZip2BlockFinder.cs:
--------------------------------------------------------------------------------
1 | using libCommon;
2 | using libDecompression.Utilities;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Diagnostics;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 |
11 | namespace libBzip2
12 | {
13 | public static class BZip2BlockFinder
14 | {
15 | private static readonly byte[] StartOfBlockMagic = [0x31, 0x41, 0x59, 0x26, 0x53, 0x59]; //31 41 59 26 53 59
16 | //private static readonly byte[] EndOfBlockMagic = [0x17, 0x72, 0x45, 0x38, 0x50, 0x90]; //17 72 45 38 50 90 //This 'end of block' marker doesn't seem to appear in the file
17 |
18 | public static IEnumerable<(long Start, long End, bool IsLast)> FindBlocks(Stream stream)
19 | {
20 | long? startOfBlock = null;
21 | foreach (var foundPosition in FindInstances(stream, StartOfBlockMagic))
22 | {
23 | if (startOfBlock != null)
24 | {
25 | var endOfBlock = foundPosition;
26 | //Debug.WriteLine($"{startOfBlock:N0} - {endOfBlock:N0}");
27 | yield return (startOfBlock.Value, endOfBlock, false);
28 | startOfBlock = null;
29 | }
30 |
31 | startOfBlock = foundPosition;
32 | }
33 |
34 | if (startOfBlock != null)
35 | {
36 | yield return (startOfBlock.Value, stream.Length, true);
37 | }
38 | }
39 |
40 | public static IEnumerable FindInstances(this Stream stream, byte[] find)
41 | {
42 | var buff = new byte[1 * 1024 * 1024];
43 | var ms = new MemoryStream(buff);
44 |
45 | while (true)
46 | {
47 | var bufferPositionInStream = stream.Position;
48 |
49 | ms.Seek(0, SeekOrigin.Begin);
50 | var bytesRead = (int)stream.CopyTo(ms, buff.Length, buff.Length);
51 | if (bytesRead == 0) break;
52 |
53 | var positionThroughCurrentBuffer = 0;
54 | while (true)
55 | {
56 | var bytesRemainingInBuffer = bytesRead - positionThroughCurrentBuffer;
57 | var subsectionToSearch = new Span(buff, positionThroughCurrentBuffer, bytesRemainingInBuffer);
58 | var foundPositionInBufferSubsection = BoyerMoore.IndexOf(subsectionToSearch, find);
59 |
60 | if (foundPositionInBufferSubsection == -1)
61 | {
62 | break;
63 | }
64 | else
65 | {
66 | var foundPositionInStream = bufferPositionInStream + positionThroughCurrentBuffer + foundPositionInBufferSubsection;
67 | yield return foundPositionInStream;
68 |
69 | positionThroughCurrentBuffer = positionThroughCurrentBuffer + foundPositionInBufferSubsection + 1;
70 | }
71 | }
72 |
73 | //just in case the search pattern is right on the border
74 | if (stream.Position == stream.Length)
75 | {
76 | break;
77 | }
78 | else
79 | {
80 | stream.Seek(-find.Length, SeekOrigin.Current);
81 | }
82 | }
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/libBzip2/libBzip2.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/libClonezilla/Cache/ClonezillaCacheManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Text;
5 | using System.Linq;
6 | using libCommon;
7 |
8 | namespace libClonezilla.Cache
9 | {
10 | public class ClonezillaCacheManager : IClonezillaCacheManager
11 | {
12 | public ClonezillaCacheManager(string clonezillaFolder, string cacheRootFolder)
13 | {
14 | ClonezillaFolder = clonezillaFolder;
15 | CacheRootFolder = cacheRootFolder;
16 | }
17 |
18 | public string ClonezillaFolder { get; }
19 | public string CacheRootFolder { get; }
20 |
21 | public IPartitionCache GetPartitionCache(string partitionName)
22 | {
23 | string imgIdFilename = Path.Combine(ClonezillaFolder, "Info-img-id.txt");
24 |
25 | string? uniqueIdForClonezillaImage;
26 |
27 | if (File.Exists(imgIdFilename))
28 | {
29 | uniqueIdForClonezillaImage = File
30 | .ReadAllLines(imgIdFilename)
31 | .First(line => line.StartsWith("IMG_ID="))
32 | .Split("=", StringSplitOptions.None)[1][..16];
33 | }
34 | else
35 | {
36 | //The file we normally use to get a unique id isn't present. Let's calculate a hash based on small files.
37 |
38 | var smallFileHashes = Directory
39 | .GetFiles(ClonezillaFolder)
40 | .Where(filename => new FileInfo(filename).Length < 1024 * 1024)
41 | .Take(100)
42 | .Select(filename => libCommon.Utility.CalculateMD5(filename))
43 | .ToString(Environment.NewLine);
44 | var smallFileHashesBytes = Encoding.UTF8.GetBytes(smallFileHashes);
45 |
46 | uniqueIdForClonezillaImage = libCommon.Utility.CalculateMD5(smallFileHashesBytes);
47 | }
48 |
49 | var clonezillaCacheFolder = Path.Combine(CacheRootFolder, uniqueIdForClonezillaImage);
50 | if (!Directory.Exists(clonezillaCacheFolder))
51 | {
52 | Directory.CreateDirectory(clonezillaCacheFolder);
53 | }
54 |
55 | var result = new PartitionCache(clonezillaCacheFolder, partitionName);
56 | return result;
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/libClonezilla/Cache/FileSystem/FileDetails.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace libClonezilla.Cache.FileSystem
6 | {
7 | public class FileDetails : FileSystemObject
8 | {
9 | public long Length;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/libClonezilla/Cache/FileSystem/FileSystemObject.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | namespace libClonezilla.Cache.FileSystem
7 | {
8 | public class FileSystemObject
9 | {
10 | public string? FullPath;
11 | public DateTime LastModifiedDate;
12 | public DateTime CreationDate;
13 |
14 | public override string ToString()
15 | {
16 | return FullPath ?? "";
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/libClonezilla/Cache/FileSystem/FolderDetails.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | namespace libClonezilla.Cache.FileSystem
7 | {
8 | public class FolderDetails : FileSystemObject
9 | {
10 | //[JsonIgnoreAttribute]
11 | //public List Children;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/libClonezilla/Cache/IClonezillaCacheManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace libClonezilla.Cache
6 | {
7 | public interface IClonezillaCacheManager
8 | {
9 | public IPartitionCache GetPartitionCache(string partitionName);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/libClonezilla/Cache/IPartitionCache.cs:
--------------------------------------------------------------------------------
1 | using lib7Zip;
2 | using libClonezilla.Cache.FileSystem;
3 | using libClonezilla.Partitions;
4 | using libCommon.Streams;
5 | using libDokan.VFS.Files;
6 | using libDokan.VFS.Folders;
7 | using libPartclone;
8 | using libPartclone.Cache;
9 | using System;
10 | using System.Collections.Generic;
11 | using System.Text;
12 |
13 | namespace libClonezilla.Cache
14 | {
15 | public interface IPartitionCache
16 | {
17 | public string GetGztoolIndexFilename();
18 | public string GetBZip2IndexFilename();
19 | public List? GetFileList();
20 | public void SetFileList(List filenames);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/libClonezilla/Cache/PartitionCache.cs:
--------------------------------------------------------------------------------
1 | using libClonezilla.Cache.FileSystem;
2 | using System.Linq;
3 | using libClonezilla.Partitions;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using System.Text;
8 | using Newtonsoft.Json;
9 | using libPartclone;
10 | using libCommon.Streams;
11 | using System.Collections.ObjectModel;
12 | using libPartclone.Cache;
13 | using lib7Zip;
14 | using libDokan.VFS.Files;
15 | using libDokan.VFS.Folders;
16 | using Serilog;
17 |
18 | namespace libClonezilla.Cache
19 | {
20 | public class PartitionCache : IPartcloneCache, IPartitionCache
21 | {
22 | public string ClonezillaCacheFolder { get; }
23 | public string PartitionName { get; }
24 |
25 | private readonly string PartcloneContentMappingFilename;
26 | private readonly string FileListFilename;
27 |
28 | public PartitionCache(string clonezillaCacheFolder, string partitionName)
29 | {
30 | ClonezillaCacheFolder = clonezillaCacheFolder;
31 | PartitionName = partitionName;
32 |
33 | PartcloneContentMappingFilename = Path.Combine(ClonezillaCacheFolder, $"{partitionName}.PartcloneContentMapping.json");
34 | FileListFilename = Path.Combine(ClonezillaCacheFolder, $"{partitionName}.Files.json");
35 | }
36 |
37 | public string GetGztoolIndexFilename()
38 | {
39 | var result = Path.Combine(ClonezillaCacheFolder, $"{PartitionName}.gztool_index.gzi");
40 | return result;
41 | }
42 |
43 | public string GetBZip2IndexFilename()
44 | {
45 | var result = Path.Combine(ClonezillaCacheFolder, $"{PartitionName}.bzip2_index.json");
46 | return result;
47 | }
48 |
49 |
50 | public List? GetPartcloneContentMapping()
51 | {
52 | List? result = null;
53 |
54 | if (File.Exists(PartcloneContentMappingFilename))
55 | {
56 | var json = File.ReadAllText(PartcloneContentMappingFilename);
57 | result = JsonConvert.DeserializeObject>(json);
58 | }
59 |
60 | return result;
61 | }
62 |
63 | public void SetPartcloneContentMapping(List contiguousRanges)
64 | {
65 | try
66 | {
67 | var json = JsonConvert.SerializeObject(contiguousRanges, Formatting.Indented);
68 | File.WriteAllText(PartcloneContentMappingFilename, json);
69 | }
70 | catch (Exception ex)
71 | {
72 | Log.Warning($"Non-fatal. Error while caching Partclone Content Mapping to {PartcloneContentMappingFilename}: {ex}");
73 | }
74 | }
75 |
76 | public List? GetFileList()
77 | {
78 | List? result = null;
79 |
80 | if (File.Exists(FileListFilename))
81 | {
82 | string json = File.ReadAllText(FileListFilename);
83 | result = JsonConvert.DeserializeObject>(json);
84 | }
85 |
86 | return result;
87 | }
88 |
89 | public void SetFileList(List filenames)
90 | {
91 | try
92 | {
93 | var json = JsonConvert.SerializeObject(filenames, Formatting.Indented);
94 | File.WriteAllText(FileListFilename, json);
95 | }
96 | catch (Exception ex)
97 | {
98 | Log.Warning($"Non-fatal. Error while caching File List to {FileListFilename}: {ex}");
99 | }
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/libClonezilla/Cache/WholeFileCacheManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace libClonezilla.Cache
9 | {
10 | public static class WholeFileCacheManager
11 | {
12 | public static string RootCacheFolder { get; private set; } = "";
13 | public static void Initialize(string cacheFolder)
14 | {
15 | RootCacheFolder = cacheFolder;
16 | Directory.CreateDirectory(RootCacheFolder);
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/libClonezilla/Decompressors/Decompressor.cs:
--------------------------------------------------------------------------------
1 | using libCommon;
2 | using libPartclone;
3 | using SharpCompress.Compressors.BZip2;
4 | using SharpCompress.Compressors.Xz;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.IO;
8 | using System.IO.Compression;
9 | using System.Linq;
10 | using System.Text;
11 | using System.Threading.Tasks;
12 | using ZstdNet;
13 |
14 | namespace libClonezilla.Decompressors
15 | {
16 | public abstract class Decompressor
17 | {
18 | public Stream CompressedStream { get; }
19 |
20 | public abstract Stream GetSequentialStream();
21 | public abstract Stream? GetSeekableStream();
22 |
23 | public Decompressor(Stream compressedStream)
24 | {
25 | CompressedStream = compressedStream;
26 | }
27 |
28 | public static Compression GetCompressionType(Stream compressedStream)
29 | {
30 | Compression result = Compression.None;
31 |
32 | var decompressors = new (Compression Compression, Func Stream)[]
33 | {
34 | (Compression.bzip2, () => new BZip2Stream(compressedStream, SharpCompress.Compressors.CompressionMode.Decompress, false)),
35 | (Compression.Gzip, () => new GZipStream(compressedStream, CompressionMode.Decompress)),
36 | (Compression.xz, () => new XZStream(compressedStream)),
37 | (Compression.Zstandard, () => new DecompressionStream(compressedStream)),
38 | };
39 |
40 | foreach (var decompressor in decompressors)
41 | {
42 | compressedStream.Seek(0, SeekOrigin.Begin);
43 |
44 | try
45 | {
46 | //extract a little and see what happens
47 | decompressor.Stream().CopyTo(Stream.Null, 32, 32);
48 | result = decompressor.Compression;
49 | break;
50 | }
51 | catch { }
52 | }
53 |
54 | compressedStream.Seek(0, SeekOrigin.Begin);
55 |
56 | return result;
57 | }
58 | }
59 |
60 | public enum Compression
61 | {
62 | bzip2,
63 | Gzip,
64 | LZ4,
65 | LZip,
66 | None,
67 | xz,
68 | Zstandard
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/libClonezilla/Decompressors/GzDecompressor.cs:
--------------------------------------------------------------------------------
1 | using libClonezilla.Cache;
2 | using libCommon.Streams.Seekable;
3 | using libGZip;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using System.IO.Compression;
8 | using System.Linq;
9 | using System.Text;
10 | using System.Threading.Tasks;
11 |
12 | namespace libClonezilla.Decompressors
13 | {
14 | public class GzDecompressor(Stream compressedStream, IPartitionCache? partitionCache) : Decompressor(compressedStream)
15 | {
16 | public IPartitionCache? PartitionCache { get; } = partitionCache;
17 |
18 | public override Stream? GetSeekableStream()
19 | {
20 | if (PartitionCache == null)
21 | {
22 | return null;
23 | }
24 | else
25 | {
26 | //this is faster for random seeks (eg. serving the full image (or file contents) in a Virtual File System)
27 | //Uses gztool to create an index for fast seek, plus a cache layer to avoid using gztool for small reads
28 |
29 | var gztoolIndexFilename = PartitionCache.GetGztoolIndexFilename();
30 | var tempgztoolIndexFilename = gztoolIndexFilename + ".wip";
31 |
32 | var gzipStreamSeekable = new GZipStreamSeekable(CompressedStream, tempgztoolIndexFilename, gztoolIndexFilename);
33 |
34 | return gzipStreamSeekable;
35 | }
36 |
37 | //return null;
38 | }
39 |
40 | public override Stream GetSequentialStream()
41 | {
42 | CompressedStream.Seek(0, SeekOrigin.Begin);
43 | var uncompressedStream = new GZipStream(CompressedStream, CompressionMode.Decompress);
44 | return uncompressedStream;
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/libClonezilla/Decompressors/LZ4Decompressor.cs:
--------------------------------------------------------------------------------
1 | using K4os.Compression.LZ4.Streams;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace libClonezilla.Decompressors
10 | {
11 | public class LZ4Decompressor : Decompressor
12 | {
13 | public LZ4Decompressor(Stream compressedStream) : base(compressedStream)
14 | {
15 | }
16 |
17 | public override Stream? GetSeekableStream()
18 | {
19 | return null;
20 | }
21 |
22 | public override Stream GetSequentialStream()
23 | {
24 | CompressedStream.Seek(0, SeekOrigin.Begin);
25 | var result = LZ4Stream.Decode(CompressedStream, null, true, false);
26 | return result;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/libClonezilla/Decompressors/LZipDecompressor.cs:
--------------------------------------------------------------------------------
1 | using K4os.Compression.LZ4.Streams;
2 | using SharpCompress.Compressors.LZMA;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.IO;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | namespace libClonezilla.Decompressors
11 | {
12 | public class LZipDecompressor : Decompressor
13 | {
14 | public LZipDecompressor(Stream compressedStream) : base(compressedStream)
15 | {
16 | }
17 |
18 | public override Stream? GetSeekableStream()
19 | {
20 | return null;
21 | }
22 |
23 | public override Stream GetSequentialStream()
24 | {
25 | CompressedStream.Seek(0, SeekOrigin.Begin);
26 | var result = new LZipStream(CompressedStream, SharpCompress.Compressors.CompressionMode.Decompress);
27 | return result;
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/libClonezilla/Decompressors/NoChangeDecompressor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace libClonezilla.Decompressors
9 | {
10 | public class NoChangeDecompressor : Decompressor
11 | {
12 | public NoChangeDecompressor(Stream compressedStream) : base(compressedStream)
13 | {
14 | }
15 |
16 | public override Stream? GetSeekableStream()
17 | {
18 | CompressedStream.Seek(0, SeekOrigin.Begin);
19 | return CompressedStream;
20 | }
21 |
22 | public override Stream GetSequentialStream()
23 | {
24 | CompressedStream.Seek(0, SeekOrigin.Begin);
25 | return CompressedStream;
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/libClonezilla/Decompressors/bzip2Decompressor.cs:
--------------------------------------------------------------------------------
1 | using libBzip2;
2 | using libClonezilla.Cache;
3 | using libCommon;
4 | using libGZip;
5 | using Serilog;
6 | using SharpCompress.Compressors.BZip2;
7 | using System;
8 | using System.Collections.Generic;
9 | using System.IO;
10 | using System.Linq;
11 | using System.Text;
12 | using System.Threading.Tasks;
13 |
14 | namespace libClonezilla.Decompressors
15 | {
16 | public class Bzip2Decompressor(Stream compressedStream, IPartitionCache? partitionCache, bool processTrailingNulls) : Decompressor(compressedStream)
17 | {
18 | public IPartitionCache? PartitionCache { get; } = partitionCache;
19 | public bool ProcessTrailingNulls { get; } = processTrailingNulls;
20 |
21 | public override Stream? GetSeekableStream()
22 | {
23 | var indexFilename = PartitionCache?.GetBZip2IndexFilename();
24 |
25 | var seekableStream = new Bzip2StreamSeekable(CompressedStream, indexFilename, ProcessTrailingNulls);
26 |
27 | return seekableStream;
28 | }
29 |
30 | public override Stream GetSequentialStream()
31 | {
32 | CompressedStream.Seek(0, SeekOrigin.Begin);
33 | var result = new BZip2Stream(CompressedStream, SharpCompress.Compressors.CompressionMode.Decompress, false);
34 |
35 | return result;
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/libClonezilla/Decompressors/xzDecompressor.cs:
--------------------------------------------------------------------------------
1 | using libCommon.Streams;
2 | using SharpCompress.Compressors.Xz;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.IO;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | namespace libClonezilla.Decompressors
11 | {
12 | #pragma warning disable IDE1006 // Naming Styles
13 | public class xzDecompressor : Decompressor
14 | #pragma warning restore IDE1006 // Naming Styles
15 | {
16 | public xzDecompressor(Stream compressedStream) : base(compressedStream)
17 | {
18 |
19 | }
20 |
21 | public override Stream? GetSeekableStream()
22 | {
23 | return null;
24 | }
25 |
26 | public override Stream GetSequentialStream()
27 | {
28 | CompressedStream.Seek(0, SeekOrigin.Begin);
29 | var result = new XZStream(CompressedStream);
30 | return result;
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/libClonezilla/Decompressors/zstdDecompressor.cs:
--------------------------------------------------------------------------------
1 | using libCommon;
2 | using libCommon.Streams.Seekable;
3 | using Serilog;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 | using ZstdNet;
11 |
12 | namespace libClonezilla.Decompressors
13 | {
14 | public class ZstdDecompressor : Decompressor
15 | {
16 | public ZstdDecompressor(Stream compressedStream) : base(compressedStream)
17 | {
18 |
19 | }
20 |
21 | public override Stream? GetSeekableStream()
22 | {
23 | return null;
24 | //For now, let's extract it to a file so that we can have fast seeking
25 | /*
26 | Log.Information($"Zstandard doesn't support random seeking. Extracting to a temporary file.");
27 |
28 | var decompressor = new DecompressionStream(CompressedStream);
29 |
30 | var tempFilename = TempUtility.GetTempFilename(true);
31 | var tempFileStream = new FileStream(tempFilename, FileMode.Open, FileAccess.ReadWrite, FileShare.None, 4096, FileOptions.DeleteOnClose);
32 |
33 | decompressor.CopyTo(tempFileStream, Buffers.ARBITARY_HUGE_SIZE_BUFFER,
34 | totalCopied =>
35 | {
36 | var totalCopiedStr = libCommon.Extensions.BytesToString(totalCopied);
37 | Log.Information($"Extracted {totalCopiedStr} of Zstandard-compressed file to {tempFilename}.");
38 | });
39 |
40 | return tempFileStream;
41 | */
42 |
43 |
44 | //FPS 04/01/2021: An experiment to park partially processed streams all over the file. Unfortunately this was still too slow
45 | /*
46 | var zstdDecompressorGenerator = new Func(() =>
47 | {
48 | var compressedStream = compressedStreamGenerator();
49 | var zstdDecompressorStream = new DecompressionStream(compressedStream);
50 |
51 | //the Zstandard decompressor doesn't track position in stream, so we have to do it for them
52 | var positionTrackerStream = new PositionTrackerStream(zstdDecompressorStream);
53 |
54 | return positionTrackerStream;
55 | });
56 |
57 | uncompressedStream = new SeekableStreamUsingNearestActioner(zstdDecompressorGenerator, totalLength, 1 * 1024 * 1024); //stations should be within one second of an actioner.
58 | */
59 | }
60 | public override Stream GetSequentialStream()
61 | {
62 | CompressedStream.Seek(0, SeekOrigin.Begin);
63 | var uncompressedStream = new DecompressionStream(CompressedStream);
64 | return uncompressedStream;
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/libClonezilla/Extractors/CachedExtractor.cs:
--------------------------------------------------------------------------------
1 | using libCommon;
2 | using System;
3 | using System.Collections.Concurrent;
4 | using System.Collections.Generic;
5 | using System.IO;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | namespace libClonezilla.Extractors
11 | {
12 | public class CachedExtractor : IExtractor
13 | {
14 | readonly ConcurrentDictionary AlreadyExtracted = new();
15 |
16 | public CachedExtractor(IExtractor baseExtractor)
17 | {
18 | BaseExtractor = baseExtractor;
19 | }
20 |
21 | public IExtractor BaseExtractor { get; }
22 |
23 | public Stream Extract(string path)
24 | {
25 | var result = AlreadyExtracted
26 | .GetOrAdd(path, path =>
27 | {
28 | Stream stream = BaseExtractor.Extract(path);
29 |
30 | return stream;
31 | });
32 |
33 | return result;
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/libClonezilla/Extractors/ExtractorUsing7z.cs:
--------------------------------------------------------------------------------
1 | using lib7Zip;
2 | using libCommon;
3 | using MountDocushare.Streams;
4 | using Serilog;
5 | using System;
6 | using System.Collections.Concurrent;
7 | using System.Collections.Generic;
8 | using System.IO;
9 | using System.Linq;
10 | using System.Text;
11 | using System.Threading.Tasks;
12 |
13 | namespace libClonezilla.Extractors
14 | {
15 | public class ExtractorUsing7z(string archiveFilename) : IExtractor
16 | {
17 | public string ArchiveFilename { get; } = archiveFilename;
18 |
19 | public Stream Extract(string pathInArchive)
20 | {
21 | /*
22 | var stream = new MemoryStream();
23 | //lock (realImageFile) //No need to synchronise here, as the PartcloneStream already protects itself from concurrent access
24 | {
25 | SevenZipUtility.ExtractFileFromArchive(ArchiveFilename, pathInArchive, stream);
26 | }
27 | stream.Seek(0, SeekOrigin.Begin);
28 | return stream;
29 | */
30 |
31 | var processStream = SevenZipUtility.ExtractFileFromArchive(ArchiveFilename, pathInArchive);
32 |
33 | //var tempStorageStream = new MemoryStream(); //can't use a MemoryStream because it has a limit of 2GB
34 | var tempFilename = TempUtility.GetTempFilename(true);
35 | var tempStorageStream = new FileStream(tempFilename, FileMode.Open, FileAccess.ReadWrite, FileShare.None, 4096, FileOptions.DeleteOnClose);
36 |
37 | var result = new SiphonStream(processStream, tempStorageStream); //this will return data as soon as it arrives from the process
38 |
39 | /*
40 | processStream.CopyTo(tempStorageStream);
41 | tempStorageStream.Seek(0, SeekOrigin.Begin);
42 | var result = tempStorageStream;
43 | */
44 |
45 | //processStream.CopyTo(tempStorageStream);
46 | //var result = tempStorageStream;
47 |
48 | return result;
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/libClonezilla/Extractors/ExtractorUsing7zFM.cs:
--------------------------------------------------------------------------------
1 | using lib7Zip;
2 | using libCommon;
3 | using System;
4 | using System.Collections.Concurrent;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 |
11 | namespace libClonezilla.Extractors
12 | {
13 | public class ExtractorUsing7zFM(string archiveFilename) : IExtractor
14 | {
15 | readonly SevenZipExtractorUsing7zFM FileManagerExtractor = new(archiveFilename);
16 |
17 | public Stream Extract(string pathInArchive)
18 | {
19 | var tempFolder = TempUtility.GetTemporaryDirectory();
20 |
21 | FileManagerExtractor.ExtractFile(pathInArchive, tempFolder);
22 |
23 | var tempFilename = Directory.GetFiles(tempFolder).First();
24 |
25 | Stream result = File.OpenRead(tempFilename); ;
26 |
27 | return result;
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/libClonezilla/Extractors/ExtractorUsingSevenZipExtractor.cs:
--------------------------------------------------------------------------------
1 | using lib7Zip;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace libClonezilla.Extractors
10 | {
11 | public class ExtractorUsingSevenZipExtractor : IExtractor
12 | {
13 | readonly SevenZipExtractorUsingSevenZipExtractor sevenZipExtractorEx;
14 |
15 | public ExtractorUsingSevenZipExtractor(Stream archiveStream)
16 | {
17 | sevenZipExtractorEx = new SevenZipExtractorUsingSevenZipExtractor(archiveStream);
18 | }
19 |
20 | public ExtractorUsingSevenZipExtractor(string archiveFilename)
21 | {
22 | sevenZipExtractorEx = new SevenZipExtractorUsingSevenZipExtractor(archiveFilename);
23 | }
24 |
25 | public Stream Extract(string pathInArchive)
26 | {
27 | var stream = new MemoryStream();
28 | sevenZipExtractorEx.ExtractFile(pathInArchive, stream);
29 | stream.Seek(0, SeekOrigin.Begin);
30 | return stream;
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/libClonezilla/Extractors/ExtractorUsingSevenZipSharp.cs:
--------------------------------------------------------------------------------
1 | using lib7Zip;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace libClonezilla.Extractors
10 | {
11 | public class ExtractorUsingSevenZipSharp : IExtractor
12 | {
13 | readonly SevenZipExtractorUsingSevenZipSharp sevenZipExtractorEx;
14 |
15 | public ExtractorUsingSevenZipSharp(Stream archiveStream)
16 | {
17 | sevenZipExtractorEx = new SevenZipExtractorUsingSevenZipSharp(archiveStream);
18 | }
19 |
20 | public ExtractorUsingSevenZipSharp(string archiveFilename)
21 | {
22 | sevenZipExtractorEx = new SevenZipExtractorUsingSevenZipSharp(archiveFilename);
23 | }
24 |
25 | public Stream Extract(string pathInArchive)
26 | {
27 | var stream = new MemoryStream();
28 | sevenZipExtractorEx.ExtractFile(pathInArchive, stream);
29 | stream.Seek(0, SeekOrigin.Begin);
30 | return stream;
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/libClonezilla/Extractors/IExtractor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace libClonezilla.Extractors
9 | {
10 | public interface IExtractor
11 | {
12 | Stream Extract(string path);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/libClonezilla/Extractors/MultiExtractor.cs:
--------------------------------------------------------------------------------
1 | using libCommon;
2 | using System;
3 | using System.Collections.Concurrent;
4 | using System.Collections.Generic;
5 | using System.IO;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | namespace libClonezilla.Extractors
11 | {
12 | public class MultiExtractor : IExtractor
13 | {
14 | public MultiExtractor(List extractors, bool forceFullRead)
15 | {
16 | Extractors = [];
17 |
18 | extractors
19 | .ForEach(extractor => Extractors.Add(extractor));
20 | ForceFullRead = forceFullRead;
21 | }
22 |
23 | public BlockingCollection Extractors { get; }
24 | public bool ForceFullRead { get; }
25 |
26 | public Stream Extract(string archiveEntryPath)
27 | {
28 | //find an idle worker
29 | var worker = Extractors.GetConsumingEnumerable().First();
30 |
31 | //do the work
32 | var result = worker.Extract(archiveEntryPath);
33 |
34 | if (ForceFullRead)
35 | {
36 | //some streams are non-blocking. We've been asked to read the stream in its entirety before calling it done
37 | result.CopyTo(Stream.Null, Buffers.ARBITARY_LARGE_SIZE_BUFFER);
38 | }
39 |
40 | //go to the back of the line
41 | Extractors.Add(worker);
42 |
43 | return result;
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/libClonezilla/Extractors/SynchronisedExtractor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace libClonezilla.Extractors
9 | {
10 | public class SynchronisedExtractor : IExtractor
11 | {
12 | public SynchronisedExtractor(IExtractor baseExtractor)
13 | {
14 | BaseExtractor = baseExtractor;
15 | }
16 |
17 | public IExtractor BaseExtractor { get; }
18 |
19 | public Stream Extract(string path)
20 | {
21 | Stream stream = BaseExtractor.Extract(path);
22 |
23 | //protect it from concurrent reads
24 | stream = Stream.Synchronized(stream);
25 |
26 | return stream;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/libClonezilla/GlobalSuppressions.cs:
--------------------------------------------------------------------------------
1 | // This file is used by Code Analysis to maintain SuppressMessage
2 | // attributes that are applied to this project.
3 | // Project-level suppressions either have no target or are given
4 | // a specific target and scoped to a namespace, type, member, etc.
5 |
6 | using System.Diagnostics.CodeAnalysis;
7 |
8 | [assembly: SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "", Scope = "module")]
9 | [assembly: SuppressMessage("Style", "IDE0130:Namespace does not match folder structure", Justification = "", Scope = "module")]
10 |
--------------------------------------------------------------------------------
/libClonezilla/PartitionContainers/ImageFiles/CompressedDriveImage.cs:
--------------------------------------------------------------------------------
1 | using lib7Zip;
2 | using libClonezilla.Decompressors;
3 | using libClonezilla.Partitions;
4 | using libCommon;
5 | using libCommon.Streams;
6 | using System;
7 | using System.Collections.Generic;
8 | using System.IO;
9 | using System.Linq;
10 | using System.Text;
11 | using System.Threading.Tasks;
12 |
13 | namespace libClonezilla.PartitionContainers.ImageFiles
14 | {
15 | public class CompressedDriveImage : PartitionContainer
16 | {
17 | public CompressedDriveImage(string containerName, string filename, List partitionsToLoad, Compression compressionInuse, bool processTrailingNulls)
18 | {
19 | ContainerName = containerName;
20 |
21 | var partitionImageFiles = SevenZipUtility.GetArchiveEntries(filename, false, true).ToList();
22 |
23 | var compressedStream = File.OpenRead(filename);
24 | var decompressorSelector = new DecompressorSelector(filename, ContainerName, compressedStream, null, compressionInuse, null, processTrailingNulls);
25 |
26 | var rawDriveStream = decompressorSelector.GetSeekableStream();
27 |
28 | var container = new RawDriveImage(containerName, partitionsToLoad, rawDriveStream, partitionImageFiles, processTrailingNulls);
29 |
30 | Partitions = container.Partitions;
31 | }
32 |
33 | public override string ContainerName { get; protected set; }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/libClonezilla/PartitionContainers/ImageFiles/CompressedImage.cs:
--------------------------------------------------------------------------------
1 | using lib7Zip;
2 | using libClonezilla.Decompressors;
3 | using libCommon;
4 | using libDokan.VFS.Files;
5 | using libDokan.VFS.Folders;
6 | using libPartclone;
7 | using System;
8 | using System.Collections.Generic;
9 | using System.IO;
10 | using System.Linq;
11 | using System.Text;
12 | using System.Threading.Tasks;
13 |
14 | namespace libClonezilla.PartitionContainers.ImageFiles
15 | {
16 | public class CompressedImage : PartitionContainer
17 | {
18 | public CompressedImage(string filename, List partitionsToLoad, bool willPerformRandomSeeking, Folder tempFolder, bool processTrailingNulls)
19 | {
20 | ContainerName = Path.GetFileNameWithoutExtension(filename);
21 |
22 | var currentFilename = filename;
23 |
24 | while (true)
25 | {
26 | Stream streamToInspect = File.OpenRead(currentFilename);
27 |
28 | //protect this stream from concurrent access
29 | streamToInspect = Stream.Synchronized(streamToInspect);
30 |
31 | var isPartcloneStream = PartcloneImageInfo.IsPartclone(streamToInspect);
32 |
33 | var compression = Decompressor.GetCompressionType(streamToInspect);
34 |
35 | if (compression == Compression.None && !isPartcloneStream)
36 | {
37 | //finally dealing with uncompressed content
38 | break;
39 | }
40 |
41 | Stream decompressedStream;
42 |
43 | if (isPartcloneStream)
44 | {
45 | decompressedStream = new PartcloneStream("", "", streamToInspect, null);
46 | }
47 | else
48 | {
49 | var decompressorSelector = new DecompressorSelector(filename, ContainerName, streamToInspect, null, compression, null, processTrailingNulls);
50 | decompressedStream = decompressorSelector.GetSeekableStream();
51 | }
52 |
53 | var tempName = Path.GetFileNameWithoutExtension(Path.GetFileName(TempUtility.GetTempFilename(false)));
54 | var virtualDecompressedFile = new StreamBackedFileEntry(tempName, tempFolder, decompressedStream);
55 |
56 | currentFilename = virtualDecompressedFile.FullPath;
57 | }
58 |
59 | var container = new RawImage(currentFilename, partitionsToLoad, ContainerName, willPerformRandomSeeking, processTrailingNulls);
60 |
61 | Partitions = container.Partitions;
62 | }
63 |
64 | public override string ContainerName { get; protected set; }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/libClonezilla/PartitionContainers/ImageFiles/CompressedPartitionImage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace libClonezilla.PartitionContainers.ImageFiles
9 | {
10 | public class CompressedPartitionImage : PartitionContainer
11 | {
12 | public CompressedPartitionImage(string containerName, Stream rawStream)
13 | {
14 | ContainerName = containerName;
15 | }
16 |
17 | public override string ContainerName { get; protected set; }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/libClonezilla/PartitionContainers/ImageFiles/ImageFile.cs:
--------------------------------------------------------------------------------
1 | using lib7Zip;
2 | using libClonezilla.Decompressors;
3 | using libClonezilla.Partitions;
4 | using libClonezilla.VFS;
5 | using libCommon;
6 | using libCommon.Streams;
7 | using libCommon.Streams.Seekable;
8 | using libDokan.VFS.Files;
9 | using libDokan.VFS.Folders;
10 | using libPartclone;
11 | using MountDocushare.Streams;
12 | using System;
13 | using System.Collections.Generic;
14 | using System.IO;
15 | using System.Linq;
16 | using System.Text;
17 | using System.Threading.Tasks;
18 |
19 | namespace libClonezilla.PartitionContainers.ImageFiles
20 | {
21 | public class ImageFile : PartitionContainer
22 | {
23 | public ImageFile(string filename, List partitionsToLoad, bool willPerformRandomSeeking, IVFS vfs, bool processTrailingNulls)
24 | {
25 | Stream mainFileStream = File.OpenRead(filename);
26 |
27 | //protect this stream from concurrent access
28 | mainFileStream = Stream.Synchronized(mainFileStream);
29 |
30 | ContainerName = Path.GetFileNameWithoutExtension(filename);
31 |
32 | //we have to work out if the image file is compressed or not
33 |
34 | PartitionContainer container;
35 |
36 | var isPartcloneStream = PartcloneImageInfo.IsPartclone(mainFileStream);
37 |
38 | if (isPartcloneStream)
39 | {
40 | container = new PartcloneFile(filename, partitionsToLoad, willPerformRandomSeeking, processTrailingNulls);
41 | }
42 | else
43 | {
44 | var compressionInUse = Decompressor.GetCompressionType(mainFileStream);
45 |
46 | if (compressionInUse == Compression.None)
47 | {
48 | container = new RawImage(filename, partitionsToLoad, ContainerName, willPerformRandomSeeking, processTrailingNulls);
49 | }
50 | else
51 | {
52 | //To inspect compressed images, we need a virtual temp folder.
53 | //Let's get one from the VFS.
54 | var tempFolder = vfs.CreateTempFolder();
55 | container = new CompressedImage(filename, partitionsToLoad, willPerformRandomSeeking, tempFolder, processTrailingNulls);
56 | }
57 | }
58 |
59 | Partitions = container.Partitions;
60 | }
61 |
62 | public override string ContainerName { get; protected set; }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/libClonezilla/PartitionContainers/ImageFiles/RawImage.cs:
--------------------------------------------------------------------------------
1 | using lib7Zip;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace libClonezilla.PartitionContainers.ImageFiles
10 | {
11 | public class RawImage : PartitionContainer
12 | {
13 | public RawImage(string filename, List partitionsToLoad, string containerName, bool willPerformRandomSeeking, bool processTrailingNulls)
14 | {
15 | Filename = filename;
16 | PartitionsToLoad = partitionsToLoad;
17 | ContainerName = containerName;
18 |
19 | var archiveEntries = SevenZipUtility.GetArchiveEntries(
20 | filename,
21 | false,
22 | true);
23 |
24 | var rawImageStream = File.OpenRead(filename);
25 | SetupFromStream(rawImageStream, archiveEntries, willPerformRandomSeeking, processTrailingNulls);
26 | }
27 |
28 | public RawImage(
29 | string filename,
30 | Stream rawImageStream,
31 | List partitionsToLoad,
32 | string containerName,
33 | IEnumerable archiveEntries,
34 | bool willPerformRandomSeeking,
35 | bool processTrailingNulls)
36 | {
37 | Filename = filename;
38 | PartitionsToLoad = partitionsToLoad;
39 | ContainerName = containerName;
40 | SetupFromStream(rawImageStream, archiveEntries, willPerformRandomSeeking, processTrailingNulls);
41 | }
42 |
43 | public void SetupFromStream(Stream rawImageStream, IEnumerable archiveEntries, bool willPerformRandomSeeking, bool processTrailingNulls)
44 | {
45 | var firstArchiveEntry = archiveEntries.FirstOrDefault();
46 |
47 | //we have to work out if this is a drive image, or a partition image
48 |
49 | var isDriveImage = firstArchiveEntry != null && !firstArchiveEntry.IsFolder && Path.GetFileNameWithoutExtension(firstArchiveEntry.Path).Equals("0") && firstArchiveEntry.Offset != null;
50 |
51 | PartitionContainer container;
52 | if (isDriveImage)
53 | {
54 | var partitionImageFiles = archiveEntries.ToList();
55 |
56 | container = new RawDriveImage(ContainerName, PartitionsToLoad, rawImageStream, partitionImageFiles, processTrailingNulls);
57 | }
58 | else
59 | {
60 | container = new RawPartitionImage(Filename, ContainerName, PartitionsToLoad, "partition0", rawImageStream, processTrailingNulls);
61 | }
62 |
63 | Partitions = container.Partitions;
64 | }
65 |
66 | public string Filename { get; }
67 | public List PartitionsToLoad { get; }
68 | public override string ContainerName { get; protected set; }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/libClonezilla/PartitionContainers/ImageFiles/RawPartitionImage.cs:
--------------------------------------------------------------------------------
1 | using libClonezilla.Decompressors;
2 | using libClonezilla.Partitions;
3 | using libCommon.Streams;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 |
11 | namespace libClonezilla.PartitionContainers.ImageFiles
12 | {
13 | public class RawPartitionImage : PartitionContainer
14 | {
15 | public RawPartitionImage(string originFilename, string containerName, List partitionsToLoad, string partitionName, Stream rawStream, bool processTrailingNulls)
16 | {
17 | ContainerName = containerName;
18 | rawStream.Seek(0, SeekOrigin.Begin);
19 |
20 | var partition = new ImageFilePartition(originFilename, this, partitionName, rawStream, rawStream.Length, Compression.None, null, true, processTrailingNulls);
21 |
22 | Partitions = [];
23 |
24 | if (partitionsToLoad.Count == 0 || partitionsToLoad.Contains(partitionName))
25 | {
26 | Partitions.Add(partition);
27 | };
28 | }
29 |
30 | public override string ContainerName { get; protected set; }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/libClonezilla/PartitionContainers/PartcloneFile.cs:
--------------------------------------------------------------------------------
1 | using libClonezilla.Decompressors;
2 | using libClonezilla.Partitions;
3 | using libPartclone;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 |
11 | namespace libClonezilla.PartitionContainers
12 | {
13 | public class PartcloneFile : PartitionContainer
14 | {
15 | public string Filename { get; }
16 |
17 | public PartcloneFile(string filename, List partitionsToLoad, bool willPerformRandomSeeking, bool processTrailingNulls)
18 | {
19 | Filename = filename;
20 |
21 | var partitionName = Path.GetFileName(filename);
22 |
23 | var partcloneStream = File.OpenRead(filename);
24 |
25 | var partcloneInfo = new PartcloneImageInfo(ContainerName, partitionName, partcloneStream, null);
26 | var uncompressedLength = partcloneInfo.Length;
27 |
28 | Partitions = [];
29 |
30 | if (partitionsToLoad.Count == 0 || partitionsToLoad.Contains(partitionName))
31 | {
32 | var partition = new PartclonePartition(
33 | filename,
34 | this,
35 | partitionName,
36 | partcloneStream,
37 | uncompressedLength,
38 | Compression.None,
39 | null,
40 | null,
41 | willPerformRandomSeeking,
42 | processTrailingNulls);
43 |
44 | Partitions.Add(partition);
45 | };
46 | }
47 |
48 | string? containerName;
49 | public override string ContainerName
50 | {
51 | get
52 | {
53 | containerName ??= Path.GetFileName(Filename) ?? throw new Exception($"Could not get container name from path: {Filename}");
54 |
55 | return containerName;
56 | }
57 |
58 | protected set
59 | {
60 | containerName = value;
61 | }
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/libClonezilla/PartitionContainers/PartitionContainer.cs:
--------------------------------------------------------------------------------
1 | using libClonezilla.Cache;
2 | using libClonezilla.Partitions;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.IO;
6 | using System.Text;
7 | using System.Linq;
8 | using lib7Zip;
9 | using libClonezilla.PartitionContainers.ImageFiles;
10 | using libDokan.VFS.Folders;
11 | using libClonezilla.VFS;
12 |
13 | namespace libClonezilla.PartitionContainers
14 | {
15 | public abstract class PartitionContainer
16 | {
17 | public List Partitions { get; protected set; } = [];
18 |
19 | public abstract string ContainerName { get; protected set; }
20 |
21 | public static PartitionContainer FromPath(string path, string cacheFolder, List partitionsToLoad, bool willPerformRandomSeeking, IVFS vfs, bool processTrailingNulls)
22 | {
23 | PartitionContainer? result = null;
24 |
25 | if (Directory.Exists(path))
26 | {
27 | var clonezillaMagicFile = Path.Combine(path, "clonezilla-img");
28 |
29 | if (File.Exists(clonezillaMagicFile))
30 | {
31 | var clonezillaCacheManager = new ClonezillaCacheManager(path, cacheFolder);
32 | result = new ClonezillaImage(path, clonezillaCacheManager, partitionsToLoad, willPerformRandomSeeking, processTrailingNulls);
33 | }
34 | }
35 | else if (File.Exists(path))
36 | {
37 | result = new ImageFile(path, partitionsToLoad, willPerformRandomSeeking, vfs, processTrailingNulls);
38 | }
39 | else
40 | {
41 | throw new Exception($"File not found: {path}");
42 | }
43 |
44 | if (result == null)
45 | {
46 | throw new Exception($"Could not determine if this is a Clonezilla folder, or a partclone file: {path}");
47 | }
48 |
49 | return result;
50 | }
51 |
52 | public static List FromPaths(List paths, string cacheFolder, List partitionsToLoad, bool willPerformRandomSeeking, IVFS vfs, bool processTrailingNulls)
53 | {
54 | var result = paths
55 | .Select(path => FromPath(path, cacheFolder, partitionsToLoad, willPerformRandomSeeking, vfs, processTrailingNulls))
56 | .ToList();
57 |
58 | return result;
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/libClonezilla/Partitions/ImageFilePartition.cs:
--------------------------------------------------------------------------------
1 | using libClonezilla.Cache;
2 | using libClonezilla.Decompressors;
3 | using libClonezilla.PartitionContainers;
4 | using Serilog;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.IO;
8 | using System.Linq;
9 | using System.Text;
10 | using System.Threading.Tasks;
11 |
12 | namespace libClonezilla.Partitions
13 | {
14 | public class ImageFilePartition : Partition
15 | {
16 | public ImageFilePartition(
17 | string originFilename,
18 | PartitionContainer container,
19 | string partitionName,
20 | Stream stream,
21 | long? uncompressedSize,
22 | Compression compressionInUse,
23 | IPartitionCache?
24 | partitionCache,
25 | bool willPerformRandomSeeking,
26 | bool processTrailingNulls) : base(container, partitionName, partitionCache, stream)
27 | {
28 | var streamName = $"[{container.ContainerName}] [{partitionName}]";
29 |
30 | Log.Information($"{streamName} Finding optimal decompressor (seekable/sequential)");
31 |
32 | var decompressorSelector = new DecompressorSelector(originFilename, streamName, stream, uncompressedSize, compressionInUse, partitionCache, processTrailingNulls);
33 |
34 | Stream decompressedStream;
35 | if (willPerformRandomSeeking)
36 | {
37 | decompressedStream = decompressorSelector.GetSeekableStream();
38 | }
39 | else
40 | {
41 | decompressedStream = decompressorSelector.GetSequentialStream();
42 | }
43 |
44 | Log.Information($"[{container.ContainerName}] [{partitionName}] Loading partition information");
45 | FullPartitionImage = decompressedStream;
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/libClonezilla/Partitions/MountedContainer.cs:
--------------------------------------------------------------------------------
1 | using libClonezilla.PartitionContainers;
2 | using libClonezilla.VFS;
3 | using libDokan.VFS;
4 | using libDokan.VFS.Files;
5 | using libDokan.VFS.Folders;
6 | using System;
7 | using System.Collections.Generic;
8 | using System.Linq;
9 | using System.Text;
10 | using System.Threading.Tasks;
11 | using static libClonezilla.Partitions.MountedPartitionImage;
12 |
13 | namespace libClonezilla.Partitions
14 | {
15 | public class MountedContainer
16 | {
17 | public MountedContainer(PartitionContainer container, Folder containerFolder, IVFS vfs, DesiredContent desiredContent)
18 | {
19 | Container = container;
20 |
21 | MountedPartitions = container
22 | .Partitions
23 | .Select(partition =>
24 | {
25 | Folder partitionFolder;
26 |
27 | if (container.Partitions.Count == 1 || desiredContent == DesiredContent.ImageFiles)
28 | {
29 | partitionFolder = containerFolder;
30 | }
31 | else
32 | {
33 | partitionFolder = new Folder(partition.PartitionName, containerFolder);
34 | }
35 |
36 | var mountedPartition = new MountedPartitionImage(partition, partitionFolder, vfs, desiredContent);
37 | return mountedPartition;
38 | })
39 | .ToList();
40 | }
41 |
42 | public PartitionContainer Container { get; }
43 |
44 | public List MountedPartitions;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/libClonezilla/Partitions/PartclonePartition.cs:
--------------------------------------------------------------------------------
1 | using libClonezilla.Cache;
2 | using libClonezilla.Decompressors;
3 | using libClonezilla.PartitionContainers;
4 | using libCommon;
5 | using libCommon.Streams;
6 | using libCommon.Streams.Seekable;
7 | using libPartclone;
8 | using libPartclone.Cache;
9 | using Serilog;
10 | using System;
11 | using System.Collections.Generic;
12 | using System.IO;
13 | using System.IO.Compression;
14 | using System.Linq;
15 | using System.Text;
16 | using System.Threading.Tasks;
17 | using ZstdNet;
18 |
19 | namespace libClonezilla.Partitions
20 | {
21 | public class PartclonePartition : Partition
22 | {
23 | public PartclonePartition(
24 | string originFilename,
25 | PartitionContainer container,
26 | string partitionName,
27 | Stream compressedPartcloneStream,
28 | long? uncompressedLength,
29 | Compression compressionInUse,
30 | IPartitionCache? partitionCache,
31 | IPartcloneCache? partcloneCache,
32 | bool willPerformRandomSeeking,
33 | bool processTrailingNulls) : base(container, partitionName, partitionCache, compressedPartcloneStream)
34 | {
35 | var streamName = $"[{container.ContainerName}] [{partitionName}]";
36 | Log.Information($"{streamName} Finding optimal decompressor (seekable/sequential)");
37 |
38 | var decompressorSelector = new DecompressorSelector(originFilename, streamName, compressedPartcloneStream, uncompressedLength, compressionInUse, partitionCache, processTrailingNulls);
39 |
40 | Stream decompressedStream;
41 | if (willPerformRandomSeeking)
42 | {
43 | decompressedStream = decompressorSelector.GetSeekableStream();
44 | }
45 | else
46 | {
47 | decompressedStream = decompressorSelector.GetSequentialStream();
48 | }
49 |
50 | Log.Information($"{streamName} Loading partition information");
51 | FullPartitionImage = new PartcloneStream(container.ContainerName, partitionName, decompressedStream, partcloneCache);
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/libClonezilla/Partitions/Partition.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO.Compression;
4 | using System.Text;
5 | using System.Linq;
6 | using System.IO;
7 | using libCommon.Streams;
8 | using libCommon;
9 | using libClonezilla.Cache;
10 | using libClonezilla.Cache.FileSystem;
11 | using Serilog;
12 | using libCommon.Streams.Sparse;
13 | using ZstdNet;
14 | using libCommon.Streams.Seekable;
15 | using lib7Zip;
16 | using libDokan.VFS.Folders;
17 | using libDokan.VFS.Files;
18 | using libClonezilla.PartitionContainers;
19 |
20 | namespace libClonezilla.Partitions
21 | {
22 | public abstract class Partition(PartitionContainer container, string partitionName, IPartitionCache? partitionCache, Stream? compressedOrigin)
23 | {
24 | public Stream? FullPartitionImage { get; protected set; }
25 | public PartitionContainer Container { get; protected set; } = container;
26 | public string PartitionName { get; protected set; } = partitionName;
27 | public IPartitionCache? PartitionCache { get; protected set; } = partitionCache;
28 |
29 | protected Stream? CompressedOrigin = compressedOrigin;
30 |
31 | public void ExtractToFile(string outputFilename, bool makeSparse)
32 | {
33 | if (FullPartitionImage == null) throw new Exception($"[{Container.ContainerName}] [{PartitionName}] Cannot extract. {nameof(FullPartitionImage)} has not been intialised.");
34 |
35 | ExtractToFile(Container.ContainerName, PartitionName, FullPartitionImage, outputFilename, makeSparse, CompressedOrigin);
36 | }
37 |
38 | public static void ExtractToFile(string containerName, string partitionName, Stream stream, string outputFilename, bool makeSparse, Stream? compressedOrigin)
39 | {
40 | Log.Information($"[{containerName}] [{partitionName}] Extracting partition to: {outputFilename}");
41 |
42 | using var fileStream = File.Create(outputFilename);
43 | StreamUtility.ExtractToFile($"[{containerName}] [{partitionName}]", compressedOrigin, stream, fileStream, makeSparse);
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/libClonezilla/Utility.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Reflection;
5 | using System.Text;
6 | using System.Linq;
7 | using libClonezilla.PartitionContainers;
8 | using libDokan.VFS.Folders;
9 | using libDokan;
10 | using Serilog;
11 | using System.Threading.Tasks;
12 | using System.Threading;
13 | using DokanNet;
14 | using libClonezilla.Partitions;
15 | using libClonezilla.VFS;
16 | using static libClonezilla.Partitions.MountedPartitionImage;
17 |
18 | namespace libClonezilla
19 | {
20 | public static class Utility
21 | {
22 | public static void LoadAllBinDirectoryAssemblies(string folder)
23 | {
24 | foreach (string dll in Directory.GetFiles(folder, "*.dll", SearchOption.AllDirectories))
25 | {
26 | try
27 | {
28 | //Assembly loadedAssembly = Assembly.LoadFile(dll);
29 | AppDomain.CurrentDomain.Load(Assembly.LoadFrom(dll).GetName());
30 | }
31 | catch (FileLoadException)
32 | { } // The Assembly has already been loaded.
33 | catch (BadImageFormatException)
34 | { } // If a BadImageFormatException exception is thrown, the file is not an assembly.
35 |
36 | }
37 | }
38 |
39 | public static List PopulateVFS(IVFS vfs, Folder containersRoot, List containers, DesiredContent desiredContent)
40 | {
41 | var totalPartitions = containers.Sum(container => container.Partitions.Count);
42 |
43 | if (totalPartitions == 0)
44 | {
45 | Log.Error("No partitions to mount. Exiting.");
46 | Environment.Exit(1);
47 | }
48 |
49 | var result = containers
50 | .Select(container =>
51 | {
52 | Folder containerFolder;
53 |
54 | if (containers.Count == 1)
55 | {
56 | containerFolder = containersRoot;
57 | }
58 | else
59 | {
60 | containerFolder = new Folder(container.ContainerName, containersRoot);
61 | }
62 |
63 | var mountedContainer = new MountedContainer(container, containerFolder, vfs, desiredContent);
64 | return mountedContainer;
65 | })
66 | .ToList();
67 |
68 | return result;
69 | }
70 |
71 | public static void WaitForFolderToExist(string folderPath)
72 | {
73 | Log.Information($"Waiting for {folderPath} to be available.");
74 | while (true)
75 | {
76 | if (Directory.Exists(folderPath))
77 | {
78 | break;
79 | }
80 | Thread.Sleep(100);
81 | }
82 | }
83 |
84 | public static bool ConsoleConfirm(string title)
85 | {
86 | ConsoleKey response;
87 | do
88 | {
89 | Console.Write($"{title} [y/n] ");
90 | response = Console.ReadKey(false).Key;
91 | if (response != ConsoleKey.Enter)
92 | {
93 | Console.WriteLine();
94 | }
95 | } while (response != ConsoleKey.Y && response != ConsoleKey.N);
96 |
97 | return (response == ConsoleKey.Y);
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/libClonezilla/VFS/IVFS.cs:
--------------------------------------------------------------------------------
1 | using libDokan.VFS.Folders;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace libClonezilla.VFS
9 | {
10 | public interface IVFS
11 | {
12 | public Folder CreateTempFolder();
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/libClonezilla/VFS/SevenZipBackedFileEntry.cs:
--------------------------------------------------------------------------------
1 | using lib7Zip;
2 | using libClonezilla.Extractors;
3 | using libDokan;
4 | using libDokan.VFS;
5 | using libDokan.VFS.Files;
6 | using libDokan.VFS.Folders;
7 | using Serilog;
8 | using SevenZip;
9 | using System;
10 | using System.Collections.Generic;
11 | using System.IO;
12 | using System.Linq;
13 | using System.Runtime.Serialization;
14 | using System.Text;
15 | using System.Text.Json.Serialization;
16 | using System.Threading.Tasks;
17 |
18 | namespace clonezilla_util.VFS
19 | {
20 | public class SevenZipBackedFileEntry : FileEntry
21 | {
22 | //for Json deserializer
23 | public SevenZipBackedFileEntry() : base("", null)
24 | {
25 | PathInArchive = "";
26 | }
27 |
28 | public SevenZipBackedFileEntry(ArchiveEntry archiveEntry, Folder? parent, IExtractor extractor) : base(Path.GetFileName(archiveEntry.Path), parent)
29 | {
30 | Extractor = extractor;
31 |
32 | Created = archiveEntry.Created;
33 | Accessed = archiveEntry.Accessed;
34 | Modified = archiveEntry.Modified;
35 | Length = archiveEntry.Size;
36 |
37 | PathInArchive = archiveEntry.Path;
38 | }
39 |
40 |
41 | [IgnoreDataMember]
42 | public IExtractor? Extractor { get; set; }
43 | public string PathInArchive { get; set; }
44 |
45 | public override Stream GetStream()
46 | {
47 | if (Extractor == null) throw new Exception($"{nameof(SevenZipBackedFileEntry)}: Extractor not initialized.");
48 |
49 | var stream = Extractor.Extract(PathInArchive);
50 | return stream;
51 | }
52 |
53 | public override string ToString()
54 | {
55 | var result = $"{Name}";
56 | return result;
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/libClonezilla/libClonezilla.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 |
7 |
8 |
9 | full
10 | true
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/libCommon/Buffers.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Buffers;
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | namespace libCommon
7 | {
8 | public static class Buffers
9 | {
10 |
11 | public const int ARBITARY_SMALL_SIZE_BUFFER = 1 * 1024 * 1024;
12 | public const int ARBITARY_MEDIUM_SIZE_BUFFER = 5 * ARBITARY_SMALL_SIZE_BUFFER;
13 | public static int ARBITARY_LARGE_SIZE_BUFFER
14 | {
15 | get
16 | {
17 | if (Environment.Is64BitProcess)
18 | {
19 | return ARBITARY_MEDIUM_SIZE_BUFFER * 10;
20 | }
21 | else
22 | {
23 | return ARBITARY_MEDIUM_SIZE_BUFFER;
24 | }
25 | }
26 | }
27 |
28 | public static int ARBITARY_HUGE_SIZE_BUFFER
29 | {
30 | get
31 | {
32 | if (Environment.Is64BitProcess)
33 | {
34 | return ARBITARY_LARGE_SIZE_BUFFER * 10;
35 | }
36 | else
37 | {
38 | return ARBITARY_MEDIUM_SIZE_BUFFER;
39 | }
40 | }
41 | }
42 |
43 | //Initialised to something big, because otherwise it defaults to 1MB and smaller.
44 | //See: https://adamsitnik.com/Array-Pool/
45 | //Always remember to return the array back into the pool.
46 | //Never trust buffer.Length
47 | public static readonly ArrayPool BufferPool = ArrayPool.Create(ARBITARY_LARGE_SIZE_BUFFER + 1, 50);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/libCommon/GlobalSuppressions.cs:
--------------------------------------------------------------------------------
1 | // This file is used by Code Analysis to maintain SuppressMessage
2 | // attributes that are applied to this project.
3 | // Project-level suppressions either have no target or are given
4 | // a specific target and scoped to a namespace, type, member, etc.
5 |
6 | using System.Diagnostics.CodeAnalysis;
7 |
8 | [assembly: SuppressMessage("Interoperability", "SYSLIB1054:Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time", Justification = "", Scope = "module")]
9 | [assembly: SuppressMessage("Globalization", "CA2101:Specify marshaling for P/Invoke string arguments", Justification = "", Scope = "module")]
10 | [assembly: SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "", Scope = "module")]
11 | [assembly: SuppressMessage("Style", "IDE0130:Namespace does not match folder structure", Justification = "", Scope = "module")]
12 |
--------------------------------------------------------------------------------
/libCommon/Lists/IRangeComparer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace libCommon.Lists
9 | {
10 | public interface IRangeComparer
11 | {
12 | ///
13 | /// Returns 0 if value is in the specified range;
14 | /// less than 0 if value is above the range;
15 | /// greater than 0 if value is below the range.
16 | ///
17 | int Compare(TRange range, TValue value);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/libCommon/Lists/LazyList.cs:
--------------------------------------------------------------------------------
1 | using Serilog;
2 | using System;
3 | using System.Collections;
4 | using System.Collections.Generic;
5 | using System.Diagnostics;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 | using static System.Reflection.Metadata.BlobBuilder;
10 |
11 | namespace libCommon.Lists
12 | {
13 | public class LazyList : IList
14 | {
15 | private readonly IEnumerator _source;
16 | private readonly List _internalList = [];
17 | private bool _isSourceExhausted = false;
18 |
19 | public bool FinishedReading
20 | {
21 | get => _isSourceExhausted;
22 | }
23 |
24 | public LazyList(IEnumerable source)
25 | {
26 | _source = source.GetEnumerator();
27 | }
28 |
29 | public T this[int index]
30 | {
31 | get
32 | {
33 | EnsureExists(index);
34 | return _internalList[index];
35 | }
36 | set
37 | {
38 | EnsureExists(index);
39 | _internalList[index] = value;
40 | }
41 | }
42 |
43 | public int Count
44 | {
45 | get
46 | {
47 | EnsureAllLoaded();
48 | return _internalList.Count;
49 | }
50 | }
51 |
52 | public int CountSoFar => _internalList.Count;
53 |
54 | private bool EnsureExists(int desiredIndex)
55 | {
56 | while (_internalList.Count <= desiredIndex && !_isSourceExhausted)
57 | {
58 | if (_source.MoveNext())
59 | {
60 | _internalList.Add(_source.Current);
61 | }
62 | else
63 | {
64 | _isSourceExhausted = true;
65 | }
66 |
67 | Log.Debug($"Currently at index {_internalList.Count - 1:N0}, seeking toward desired index {desiredIndex:N0}");
68 | }
69 |
70 |
71 | if (desiredIndex >= _internalList.Count)
72 | {
73 | return false;
74 | }
75 |
76 | return true;
77 | }
78 |
79 | private void EnsureAllLoaded()
80 | {
81 | while (!_isSourceExhausted)
82 | {
83 | if (_source.MoveNext())
84 | {
85 | _internalList.Add(_source.Current);
86 | }
87 | else
88 | {
89 | _isSourceExhausted = true;
90 | }
91 | }
92 | }
93 |
94 | // Other IList methods would need to be implemented here.
95 |
96 | public void Add(T item) => throw new NotSupportedException();
97 | public void Clear() => throw new NotSupportedException();
98 | public bool Contains(T item) => _internalList.Contains(item);
99 | public void CopyTo(T[] array, int arrayIndex) => _internalList.CopyTo(array, arrayIndex);
100 | public bool Remove(T item) => throw new NotSupportedException();
101 | public IEnumerator GetEnumerator()
102 | {
103 | int i = 0;
104 | while (EnsureExists(i))
105 | {
106 | var block = this[i];
107 | yield return block;
108 | i++;
109 |
110 | Log.Debug($"GetEnumerator(): FinishedReading = {FinishedReading}, i = {i:N0}");
111 | }
112 | }
113 |
114 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
115 | public int IndexOf(T item) => _internalList.IndexOf(item);
116 | public void Insert(int index, T item) => throw new NotSupportedException();
117 | public bool IsReadOnly => false;
118 | public void RemoveAt(int index) => throw new NotSupportedException();
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/libCommon/Logging/SuppressConsecutiveDuplicateFilter.cs:
--------------------------------------------------------------------------------
1 | using Serilog.Core;
2 | using Serilog.Events;
3 |
4 | namespace libCommon.Logging
5 | {
6 | public class SuppressConsecutiveDuplicateFilter : ILogEventFilter
7 | {
8 | private string? _lastMessage;
9 |
10 | public bool IsEnabled(LogEvent logEvent)
11 | {
12 | var current = logEvent.RenderMessage();
13 | if (current == _lastMessage)
14 | {
15 | return false;
16 | }
17 |
18 | _lastMessage = current;
19 | return true;
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/libCommon/Streams/IReadSuggestor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace libCommon.Streams
6 | {
7 | public interface IReadSuggestor
8 | {
9 | (long Start, long End) GetRecommendation(long start);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/libCommon/Streams/IndependentStream.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace libCommon.Streams
9 | {
10 | //Independantly uses the provided the stream, keeping track of its on Position and uses the underlying stream in a synchronised manner
11 | public class IndependentStream(Stream baseStream, object readLock) : Stream
12 | {
13 | public override bool CanRead => true;
14 |
15 | public override bool CanSeek => true;
16 |
17 | public override bool CanWrite => false;
18 |
19 | public override long Length => BaseStream.Length;
20 |
21 | long positionInThisVirtualStream = 0;
22 | public override long Position
23 | {
24 | get
25 | {
26 | return positionInThisVirtualStream;
27 | }
28 |
29 | set
30 | {
31 | lock (ReadLock)
32 | {
33 | Seek(value, SeekOrigin.Begin);
34 | }
35 | }
36 | }
37 | public Stream BaseStream { get; } = Synchronized(baseStream);
38 | public object ReadLock { get; } = readLock;
39 |
40 | public override void Flush() => BaseStream.Flush();
41 |
42 | public override int Read(byte[] buffer, int offset, int count)
43 | {
44 | lock (ReadLock)
45 | {
46 | //make sure the base stream is where we expect it to be
47 | BaseStream.Seek(positionInThisVirtualStream, SeekOrigin.Begin);
48 |
49 | var toRead = count;
50 |
51 | var bytesRead = BaseStream.Read(buffer, offset, toRead);
52 |
53 | positionInThisVirtualStream = BaseStream.Position;
54 |
55 | return bytesRead;
56 | }
57 | }
58 |
59 | public override long Seek(long offset, SeekOrigin origin)
60 | {
61 | lock (ReadLock)
62 | {
63 | long newAbsolutePosition = 0;
64 |
65 | switch (origin)
66 | {
67 | case SeekOrigin.Begin:
68 | newAbsolutePosition = offset;
69 | break;
70 |
71 | case SeekOrigin.Current:
72 | newAbsolutePosition = positionInThisVirtualStream + offset;
73 | break;
74 |
75 | case SeekOrigin.End:
76 | newAbsolutePosition = Length + offset;
77 | break;
78 | }
79 |
80 | BaseStream.Seek(newAbsolutePosition, SeekOrigin.Begin);
81 |
82 | positionInThisVirtualStream = BaseStream.Position;
83 | return positionInThisVirtualStream;
84 | }
85 | }
86 |
87 | public override void SetLength(long value)
88 | {
89 | throw new NotImplementedException();
90 | }
91 |
92 | public override void Write(byte[] buffer, int offset, int count)
93 | {
94 | throw new NotImplementedException();
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/libCommon/Streams/Multistream.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Text;
5 | using System.Linq;
6 | using System.Diagnostics;
7 |
8 | namespace libCommon.Streams
9 | {
10 | public class Multistream : Stream
11 | {
12 | long position = 0;
13 |
14 | public Multistream(IEnumerable substreams)
15 | {
16 | long sizeSum = 0;
17 | Substreams = substreams
18 | .Select((stream, index) => new Substream(
19 | sizeSum,
20 | sizeSum += stream.Length,
21 | stream,
22 | index))
23 | .ToList();
24 | }
25 |
26 | public override bool CanRead => true;
27 |
28 | public override bool CanSeek => true;
29 |
30 | public override bool CanWrite => false;
31 |
32 | public override long Length
33 | {
34 | get
35 | {
36 | var result = Substreams.Sum(substream => substream.Length);
37 | return result;
38 | }
39 | }
40 |
41 | public override long Position
42 | {
43 | get => position;
44 | set => Seek(value, SeekOrigin.Begin);
45 | }
46 |
47 | public List Substreams { get; }
48 |
49 | public override void Flush()
50 | {
51 | throw new NotImplementedException();
52 | }
53 |
54 |
55 | public override int Read(byte[] buffer, int offset, int count)
56 | {
57 |
58 | var substream = Substreams.FirstOrDefault(s => Position >= s.Start && Position < s.End);
59 |
60 | if (substream == null)
61 | {
62 | return 0;
63 | }
64 |
65 | //determine where we should start reading in the substream
66 | var positionInSubstream = Position - substream.Start;
67 | substream.Stream.Seek(positionInSubstream, SeekOrigin.Begin);
68 |
69 |
70 | var bytesRead = substream.Stream.Read(buffer, offset, count);
71 |
72 | position += bytesRead;
73 |
74 | return bytesRead;
75 | }
76 |
77 | public override long Seek(long offset, SeekOrigin origin)
78 | {
79 | switch (origin)
80 | {
81 | case SeekOrigin.Begin:
82 | position = offset;
83 | break;
84 |
85 | case SeekOrigin.Current:
86 | position += offset;
87 | break;
88 |
89 | case SeekOrigin.End:
90 | position = Length - offset;
91 | break;
92 | }
93 |
94 | return position;
95 | }
96 |
97 | public override void SetLength(long value)
98 | {
99 | throw new NotImplementedException();
100 | }
101 |
102 | public override void Write(byte[] buffer, int offset, int count)
103 | {
104 | throw new NotImplementedException();
105 | }
106 | }
107 |
108 | public class Substream(long start, long end, Stream stream, int streamIndex = 0)
109 | {
110 | public long Start = start;
111 | public long End = end;
112 | public long Length => End - Start;
113 | public Stream Stream = stream;
114 | public int StreamIndex = streamIndex;
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/libCommon/Streams/PositionTrackerStream.cs:
--------------------------------------------------------------------------------
1 | using Serilog;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace libCommon.Streams
10 | {
11 | public class PositionTrackerStream : Stream
12 | {
13 | public PositionTrackerStream()
14 | {
15 |
16 | }
17 |
18 | public override bool CanRead => false;
19 |
20 | public override bool CanSeek => true;
21 |
22 | public override bool CanWrite => true;
23 |
24 | long length = 0;
25 | public override long Length => length;
26 |
27 | long position = 0;
28 | readonly object positionLock = new();
29 | public override long Position { get => position; set => throw new NotImplementedException(); }
30 |
31 | public override void Flush() => throw new NotImplementedException();
32 |
33 | public override int Read(byte[] buffer, int offset, int count) => throw new NotImplementedException();
34 |
35 | public override long Seek(long offset, SeekOrigin origin)
36 | {
37 | switch (origin)
38 | {
39 | case SeekOrigin.Begin:
40 | lock (positionLock)
41 | {
42 | position = offset;
43 | }
44 | break;
45 |
46 | case SeekOrigin.Current:
47 | lock (positionLock)
48 | {
49 | position += offset;
50 | }
51 | break;
52 |
53 | case SeekOrigin.End:
54 | lock (positionLock)
55 | {
56 | position = Length - offset;
57 | }
58 | break;
59 | }
60 |
61 | return position;
62 | }
63 |
64 |
65 | public override void SetLength(long value) => length = value;
66 |
67 | public override void Write(byte[] buffer, int offset, int count)
68 | {
69 | ArgumentNullException.ThrowIfNull(buffer);
70 | if (offset < 0 || offset > buffer.Length) throw new ArgumentOutOfRangeException(nameof(offset), "Offset is out of range.");
71 | if (count < 0 || (offset + count) > buffer.Length) throw new ArgumentOutOfRangeException(nameof(count), "Count is out of range.");
72 |
73 | lock (positionLock)
74 | {
75 | position += count;
76 | if (position > Length)
77 | {
78 | length = position;
79 | }
80 | }
81 | }
82 |
83 | public override string ToString()
84 | {
85 | var result = $"Position: {Position:N0}";
86 | return result;
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/libCommon/Streams/Sparse/ISparseAwareReader.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace libCommon.Streams
9 | {
10 | public interface ISparseAwareReader
11 | {
12 | public Stream Stream { get; }
13 | public bool LatestReadWasAllNull { get; set; }
14 | public bool StopReadingWhenRemainderOfFileIsNull { get; set; }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/libCommon/Streams/Sparse/ISparseAwareWriter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace libCommon.Streams.Sparse
9 | {
10 | public interface ISparseAwareWriter
11 | {
12 | public Stream Stream { get; }
13 | public bool ExplicitlyWriteNullBytes { get; set; }
14 |
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/libCommon/Streams/Sparse/SparesAwareReadStream.cs:
--------------------------------------------------------------------------------
1 | using libCommon.Streams;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace libCommon.Streams.Sparse
10 | {
11 | public class SparseAwareReader(Stream stream) : Stream, ISparseAwareReader
12 | {
13 | public Stream Stream { get; } = stream;
14 | public bool LatestReadWasAllNull { get; set; } = false;
15 | public bool StopReadingWhenRemainderOfFileIsNull { get; set; } = false;
16 |
17 | public override bool CanRead => Stream.CanRead;
18 |
19 | public override bool CanSeek => Stream.CanSeek;
20 |
21 | public override bool CanWrite => Stream.CanWrite;
22 |
23 | public override long Length => Stream.Length;
24 |
25 | public override long Position { get => Stream.Position; set => Stream.Position = value; }
26 |
27 | public static bool IsAllZerosLINQParallel(byte[] data, int offset, int count)
28 | {
29 | IEnumerable dat = data;
30 | if (offset > 0)
31 | {
32 | dat = dat.Skip(offset);
33 | }
34 |
35 | if (count != data.Length)
36 | {
37 | dat = dat.Take(count);
38 | }
39 |
40 | var result = dat
41 | .AsParallel()
42 | .WithDegreeOfParallelism(Environment.ProcessorCount)
43 | .All(b => b == 0x0);
44 |
45 | return result;
46 | }
47 |
48 | //This is up to 3x faster than IsAllZerosLINQ.
49 | /*
50 | public static unsafe bool IsAllZerosUnsafe(byte[] data, int offset, int count)
51 | {
52 | fixed (byte* p = data)
53 | {
54 | byte* start = p + offset;
55 | byte* end = start + count;
56 | for (byte* current = start; current < end; current++)
57 | {
58 | if (*current != 0) return false;
59 | }
60 | }
61 | return true;
62 | }
63 | */
64 |
65 | public override int Read(byte[] buffer, int offset, int count)
66 | {
67 | var bytesRead = Stream.Read(buffer, offset, count);
68 |
69 | LatestReadWasAllNull = IsAllZerosLINQParallel(buffer, offset, count);
70 |
71 | return bytesRead;
72 | }
73 |
74 | public override void Flush()
75 | {
76 | Stream.Flush();
77 | }
78 |
79 | public override long Seek(long offset, SeekOrigin origin)
80 | {
81 | var result = Stream.Seek(offset, origin);
82 | return result;
83 | }
84 |
85 | public override void SetLength(long value)
86 | {
87 | Stream.SetLength(value);
88 | }
89 |
90 | public override void Write(byte[] buffer, int offset, int count)
91 | {
92 | Stream.Write(buffer, offset, count);
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/libCommon/Streams/Sparse/SparseAwareWriteStream.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace libCommon.Streams.Sparse
9 | {
10 | public class SparseAwareWriteStream(Stream stream, bool explicitlyWriteNullBytes) : ISparseAwareWriter
11 | {
12 | public Stream Stream { get; } = stream;
13 | public bool ExplicitlyWriteNullBytes { get; set; } = explicitlyWriteNullBytes;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/libCommon/Streams/SubStream.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Text;
5 |
6 | namespace libCommon.Streams
7 | {
8 | public class SubStream : Stream
9 | {
10 | public SubStream(IndependentStream baseStream, long startByte, long endByte)
11 | {
12 | BaseStream = baseStream;
13 | StartByte = startByte;
14 | EndByte = endByte;
15 |
16 | Seek(0, SeekOrigin.Begin);
17 | }
18 | public override bool CanRead => true;
19 |
20 | public override bool CanSeek => true;
21 |
22 | public override bool CanWrite => false;
23 |
24 | public override long Length
25 | {
26 | get
27 | {
28 | var result = EndByte - StartByte;
29 | return result;
30 | }
31 | }
32 |
33 | public override long Position
34 | {
35 | get => BaseStream.Position - StartByte;
36 | set => Seek(value, SeekOrigin.Begin);
37 | }
38 | public Stream BaseStream { get; }
39 | public long StartByte { get; }
40 | public long EndByte { get; }
41 |
42 | public override void Flush()
43 | {
44 | throw new NotImplementedException();
45 | }
46 |
47 | public override int Read(byte[] buffer, int offset, int count)
48 | {
49 | //if (Position == Length) return 0;
50 |
51 | var bytesLeftInVirtualFile = Length - Position;
52 | //var bytesLeftInBaseStream = BaseStream.Length - BaseStream.Position;
53 |
54 | if (BaseStream.Length != 0 && Position >= BaseStream.Length)
55 | {
56 | //we are beyond the original stream. Just return blanks
57 | var toClear = (int)Math.Min(bytesLeftInVirtualFile, count);
58 | Array.Clear(buffer, offset, toClear);
59 | return toClear;
60 | }
61 |
62 | var toRead = count;
63 | toRead = (int)Math.Min(toRead, bytesLeftInVirtualFile);
64 |
65 | var result = BaseStream.Read(buffer, offset, toRead);
66 | return result;
67 | }
68 |
69 | public override long Seek(long offset, SeekOrigin origin)
70 | {
71 | switch (origin)
72 | {
73 | case SeekOrigin.Begin:
74 | BaseStream.Seek(StartByte + offset, origin);
75 | break;
76 |
77 | case SeekOrigin.Current:
78 | BaseStream.Seek(offset, origin);
79 | break;
80 |
81 | case SeekOrigin.End:
82 | BaseStream.Seek(EndByte + offset, origin);
83 | break;
84 | }
85 |
86 | return Position;
87 | }
88 |
89 | public override void SetLength(long value)
90 | {
91 | throw new NotImplementedException();
92 | }
93 |
94 | public override void Write(byte[] buffer, int offset, int count)
95 | {
96 | throw new NotImplementedException();
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/libCommon/TempUtility.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace libCommon
9 | {
10 | public static class TempUtility
11 | {
12 | static string tempRoot = Path.GetTempPath();
13 | public static string TempRoot
14 | {
15 | get
16 | {
17 | lock (tempRoot)
18 | {
19 | if (!Directory.Exists(tempRoot))
20 | {
21 | Directory.CreateDirectory(tempRoot);
22 | }
23 | }
24 | return tempRoot;
25 | }
26 |
27 | set
28 | {
29 | tempRoot = value;
30 | }
31 | }
32 |
33 | static readonly List Folders = [];
34 | static readonly List Files = [];
35 |
36 | public static string GetTemporaryDirectory()
37 | {
38 | string tempDirectory = Path.Combine(TempRoot, Path.GetFileNameWithoutExtension(Path.GetRandomFileName()));
39 | Directory.CreateDirectory(tempDirectory);
40 |
41 | lock (Folders)
42 | {
43 | Folders.Add(tempDirectory);
44 | }
45 |
46 | return tempDirectory;
47 | }
48 |
49 | public static string GetTempFilename(bool createEmptyFile)
50 | {
51 | var tempFilename = Path.Combine(TempRoot, Path.GetRandomFileName());
52 |
53 | if (createEmptyFile)
54 | {
55 | File.Create(tempFilename).Close();
56 | File.SetAttributes(tempFilename, FileAttributes.Temporary);
57 | }
58 |
59 | lock (Files)
60 | {
61 | Files.Add(tempFilename);
62 | }
63 |
64 | return tempFilename;
65 | }
66 |
67 | public static void Cleanup()
68 | {
69 | lock (Folders)
70 | {
71 | Folders
72 | .ForEach(folder =>
73 | {
74 | try
75 | {
76 | Directory.Delete(folder, true);
77 | }
78 | catch { }
79 | });
80 | }
81 |
82 | lock (Files)
83 | {
84 | Files
85 | .ForEach(filename =>
86 | {
87 | try
88 | {
89 | File.Delete(filename);
90 | }
91 | catch { }
92 | });
93 | }
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/libCommon/Utility.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Management;
4 | using System.Text;
5 | using System.Linq;
6 | using System.IO;
7 | using System.Security.Cryptography;
8 |
9 | namespace libCommon
10 | {
11 | public static class Utility
12 | {
13 | public static long GetTotalRamSizeBytes()
14 | {
15 | var gcMemoryInfo = GC.GetGCMemoryInfo();
16 | return gcMemoryInfo.TotalAvailableMemoryBytes;
17 | }
18 |
19 | public static bool IsOnNTFS(string filename)
20 | {
21 | //Get all the drives on the local machine.
22 | var allDrives = DriveInfo.GetDrives();
23 |
24 | //Get the path root.
25 | var pathRoot = Path.GetPathRoot(filename);
26 | //Find the drive based on the path root.
27 | var driveBasedOnPath = allDrives.FirstOrDefault(d => d.RootDirectory.Name.Equals(pathRoot));
28 | //Determine if NTFS
29 | var isNTFS = driveBasedOnPath != null && driveBasedOnPath.DriveFormat == "NTFS";
30 |
31 | return isNTFS;
32 | }
33 |
34 | public static string Absolutify(string relativePath)
35 | {
36 | var result = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, relativePath);
37 | return result;
38 | }
39 |
40 | public static string CalculateMD5(string filename)
41 | {
42 | using var md5 = MD5.Create();
43 | using var stream = File.OpenRead(filename);
44 | var hash = md5.ComputeHash(stream);
45 | return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
46 | }
47 |
48 | public static string CalculateMD5(Stream stream)
49 | {
50 | stream.Seek(0, SeekOrigin.Begin);
51 | using var md5 = MD5.Create();
52 | var hash = md5.ComputeHash(stream);
53 | return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
54 | }
55 |
56 | public static string CalculateMD5(byte[] bytes)
57 | {
58 | var hash = MD5.HashData(bytes);
59 | return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/libCommon/libCommon.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | False
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/libDecompression/Lists/MappingComparer.cs:
--------------------------------------------------------------------------------
1 | using libCommon.Lists;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace libDecompression.Lists
9 | {
10 | public class MappingComparer : IRangeComparer
11 | {
12 | public int Compare(Mapping range, long value)
13 | {
14 | if (value < range.UncompressedStartByte) return 1;
15 | if (value > range.UncompressedEndByte) return -1;
16 | return 0;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/libDecompression/Utilities/BoyerMoore.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace libDecompression.Utilities
8 | {
9 | public static class BoyerMoore
10 | {
11 | public static int IndexOf(Span haystack, byte[] needle)
12 | {
13 | if (needle.Length == 0)
14 | {
15 | return 0;
16 | }
17 |
18 | int[] charTable = MakeCharTable(needle);
19 | int[] offsetTable = MakeOffsetTable(needle);
20 | for (int i = needle.Length - 1; i < haystack.Length;)
21 | {
22 | int j;
23 | for (j = needle.Length - 1; needle[j] == haystack[i]; --i, --j)
24 | {
25 | if (j == 0)
26 | {
27 | return i;
28 | }
29 | }
30 |
31 | i += Math.Max(offsetTable[needle.Length - 1 - j], charTable[haystack[i]]);
32 | }
33 |
34 | return -1;
35 | }
36 |
37 | private static int[] MakeCharTable(byte[] needle)
38 | {
39 | const int ALPHABET_SIZE = 256;
40 | int[] table = new int[ALPHABET_SIZE];
41 | for (int i = 0; i < table.Length; ++i)
42 | {
43 | table[i] = needle.Length;
44 | }
45 |
46 | for (int i = 0; i < needle.Length - 1; ++i)
47 | {
48 | table[needle[i]] = needle.Length - 1 - i;
49 | }
50 |
51 | return table;
52 | }
53 |
54 | private static int[] MakeOffsetTable(byte[] needle)
55 | {
56 | int[] table = new int[needle.Length];
57 | int lastPrefixPosition = needle.Length;
58 | for (int i = needle.Length - 1; i >= 0; --i)
59 | {
60 | if (IsPrefix(needle, i + 1))
61 | {
62 | lastPrefixPosition = i + 1;
63 | }
64 |
65 | table[needle.Length - 1 - i] = lastPrefixPosition - i + needle.Length - 1;
66 | }
67 |
68 | for (int i = 0; i < needle.Length - 1; ++i)
69 | {
70 | int slen = SuffixLength(needle, i);
71 | table[slen] = needle.Length - 1 - i + slen;
72 | }
73 |
74 | return table;
75 | }
76 |
77 | private static bool IsPrefix(byte[] needle, int p)
78 | {
79 | for (int i = p, j = 0; i < needle.Length; ++i, ++j)
80 | {
81 | if (needle[i] != needle[j])
82 | {
83 | return false;
84 | }
85 | }
86 |
87 | return true;
88 | }
89 |
90 | private static int SuffixLength(byte[] needle, int p)
91 | {
92 | int len = 0;
93 | for (int i = p, j = needle.Length - 1; i >= 0 && needle[i] == needle[j]; --i, --j)
94 | {
95 | len += 1;
96 | }
97 |
98 | return len;
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/libDecompression/libDecompression.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/libDokan/Extensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace libDokan
8 | {
9 | public static class Extensions
10 | {
11 | public static IEnumerable Recurse(this IEnumerable source, Func> childSelector, bool depthFirst = false)
12 | {
13 | List queue = new(source);
14 |
15 | while (queue.Count > 0)
16 | {
17 | var item = queue[0];
18 | queue.RemoveAt(0);
19 |
20 | var children = childSelector(item);
21 |
22 | if (depthFirst)
23 | {
24 | queue.InsertRange(0, children);
25 | }
26 | else
27 | {
28 | queue.AddRange(children);
29 | }
30 |
31 | yield return item;
32 | }
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/libDokan/GlobalSuppressions.cs:
--------------------------------------------------------------------------------
1 | // This file is used by Code Analysis to maintain SuppressMessage
2 | // attributes that are applied to this project.
3 | // Project-level suppressions either have no target or are given
4 | // a specific target and scoped to a namespace, type, member, etc.
5 |
6 | using System.Diagnostics.CodeAnalysis;
7 |
8 | [assembly: SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "", Scope = "module")]
9 | [assembly: SuppressMessage("Performance", "SYSLIB1045:Convert to 'GeneratedRegexAttribute'.", Justification = "", Scope = "module")]
10 | [assembly: SuppressMessage("Style", "IDE0130:Namespace does not match folder structure", Justification = "", Scope = "module")]
11 |
--------------------------------------------------------------------------------
/libDokan/Processes/ProcInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace libDokan.Processes
9 | {
10 | public class ProcInfo
11 | {
12 | public ProcInfo(int PID)
13 | {
14 | this.PID = PID;
15 |
16 | Proc = new Lazy(() =>
17 | {
18 | var process = Process.GetProcessById(PID);
19 | return process;
20 | });
21 | }
22 |
23 | Lazy Proc { get; }
24 |
25 | public int PID { get; }
26 |
27 | public string Name => Proc.Value.ProcessName;
28 |
29 | public string FullExePath
30 | {
31 | get
32 | {
33 | string result;
34 |
35 | try
36 | {
37 | result = Proc.Value.MainModule?.FileName ?? "";
38 | }
39 | catch
40 | {
41 | result = "Not accessible";
42 | }
43 |
44 | return result;
45 | }
46 | }
47 |
48 | public string ExeFilename => Path.GetFileName(FullExePath);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/libDokan/Utility.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace libDokan
8 | {
9 | public static class Utility
10 | {
11 |
12 | public static string GetAvailableDriveLetter(bool alphabetical = false)
13 | {
14 | var existingLetters = Directory.GetLogicalDrives();
15 |
16 | var candidates = Enumerable.Range('A', 26);
17 | if (!alphabetical) candidates = candidates.Reverse();
18 |
19 | var availableLetter = candidates
20 | .Select(i => $"{(char)i}")
21 | .FirstOrDefault(candidate => !existingLetters.Any(existing => candidate.Equals(existing, StringComparison.CurrentCultureIgnoreCase))) ?? throw new Exception($"Could not find an available drive letter.");
22 |
23 | availableLetter += @":\";
24 |
25 | return availableLetter;
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/libDokan/VFS/FileSystemEntry.cs:
--------------------------------------------------------------------------------
1 | using DokanNet;
2 | using libCommon;
3 | using libDokan.Processes;
4 | using libDokan.VFS.Folders;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 | using System.Runtime.Serialization;
9 | using System.Text;
10 | using System.Text.Json.Serialization;
11 | using System.Threading.Tasks;
12 |
13 | namespace libDokan.VFS
14 | {
15 | public abstract class FileSystemEntry
16 | {
17 | public string Name { get; set; }
18 |
19 | Folder? parent;
20 | public Folder? Parent
21 | {
22 | get
23 | {
24 | return parent;
25 | }
26 | set
27 | {
28 | parent = value;
29 | parent?.AddChild(this);
30 | }
31 | }
32 | public bool Hidden { get; set; } = false;
33 | public bool System { get; set; } = false;
34 |
35 | public FileSystemEntry(string name, Folder? parent)
36 | {
37 | Name = name;
38 | Parent = parent;
39 | }
40 |
41 | public DateTime Modified;
42 | public DateTime Created;
43 | public DateTime Accessed;
44 |
45 | protected abstract FileInformation ToFileInfo();
46 |
47 | public List Ancestors
48 | {
49 | get
50 | {
51 | var ancestors = this
52 | .Recurse(ancestor => ancestor.Parent)
53 | .Reverse()
54 | .ToList();
55 |
56 | return ancestors;
57 | }
58 | }
59 |
60 | public bool IsAccessibleToProcess(int requestPID)
61 | {
62 | //check if any of the ancestors are restricted
63 |
64 | var restrictedAncestors = Ancestors
65 | .OfType()
66 | .ToList();
67 |
68 | var procInfo = new ProcInfo(requestPID);
69 |
70 | bool isRestricted = restrictedAncestors
71 | .Any(ancestor => !ancestor.IsProcessPermitted(procInfo));
72 |
73 | return isRestricted;
74 | }
75 |
76 | public string FullPath
77 | {
78 | get
79 | {
80 | var folderPath = Ancestors
81 | .Where(a => a is not RootFolder)
82 | .Select(a => a.Name)
83 | .ToString("\\");
84 |
85 | var root = Ancestors
86 | .OfType()
87 | .FirstOrDefault()?.MountPoint ?? "";
88 |
89 | var fullPath = Path.Combine(root, folderPath);
90 |
91 | return fullPath;
92 | }
93 | }
94 |
95 | public FileInformation ToFileInformation()
96 | {
97 | var result = ToFileInfo();
98 |
99 | if (Hidden) result.Attributes |= FileAttributes.Hidden;
100 | if (System) result.Attributes |= FileAttributes.System;
101 |
102 | result.FileName = Name;
103 | result.CreationTime = Created;
104 | result.LastAccessTime = Accessed;
105 | result.LastWriteTime = Modified;
106 |
107 | return result;
108 | }
109 |
110 | public override string ToString()
111 | {
112 | var result = Name;
113 | return result;
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/libDokan/VFS/Files/FileEntry.cs:
--------------------------------------------------------------------------------
1 | using DokanNet;
2 | using libDokan.VFS.Folders;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace libDokan.VFS.Files
10 | {
11 | public abstract class FileEntry : FileSystemEntry
12 | {
13 | public long Length { get; set; }
14 |
15 | public abstract Stream GetStream();
16 |
17 | public FileEntry(string name, Folder? parent) : base(name, parent)
18 | {
19 | }
20 |
21 | protected override FileInformation ToFileInfo()
22 | {
23 | var result = new FileInformation
24 | {
25 | Length = Length,
26 | };
27 |
28 | return result;
29 | }
30 |
31 | public override string ToString()
32 | {
33 | var result = $"{Name}";
34 | return result;
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/libDokan/VFS/Files/StreamBackedFileEntry.cs:
--------------------------------------------------------------------------------
1 | using libDokan.VFS.Folders;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace libDokan.VFS.Files
9 | {
10 | public class StreamBackedFileEntry : FileEntry
11 | {
12 | readonly Stream? Stream;
13 | readonly Func? StreamFactory;
14 |
15 | public StreamBackedFileEntry(string name, Folder? folder, Stream stream) : base(name, folder)
16 | {
17 | Stream = stream;
18 | Length = stream.Length;
19 | }
20 |
21 | public StreamBackedFileEntry(string name, Folder? folder, Func streamFactory, long fileSize) : base(name, folder)
22 | {
23 | StreamFactory = streamFactory;
24 | Length = fileSize;
25 | }
26 |
27 | public override Stream GetStream()
28 | {
29 | Stream? result = null;
30 |
31 | if (Stream != null)
32 | {
33 | result = Stream;
34 | } else if (StreamFactory != null)
35 | {
36 | result = StreamFactory();
37 | }
38 |
39 | if (result == null) throw new Exception($"Could not retrieve a stream for {Name}");
40 |
41 | return result;
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/libDokan/VFS/Folders/Folder.cs:
--------------------------------------------------------------------------------
1 | using DokanNet;
2 | using libDokan.VFS.Files;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.IO.Enumeration;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | namespace libDokan.VFS.Folders
11 | {
12 | public class Folder : FileSystemEntry
13 | {
14 | readonly List children = [];
15 | public IEnumerable Children => children;
16 |
17 | public void AddChild(FileSystemEntry entry)
18 | {
19 | if (!children.Contains(entry))
20 | {
21 | children.Add(entry);
22 | entry.Parent = this;
23 | }
24 | }
25 |
26 | public void AddChildren(IEnumerable entries)
27 | {
28 | entries
29 | .ToList()
30 | .ForEach(entry => AddChild(entry));
31 | }
32 |
33 | public Folder(string name, Folder? parent) : base(name, parent)
34 | {
35 | }
36 |
37 | protected override FileInformation ToFileInfo()
38 | {
39 | var result = new FileInformation();
40 |
41 | result.Attributes |= FileAttributes.Directory;
42 |
43 | return result;
44 | }
45 |
46 | public override string ToString()
47 | {
48 | var result = $"{Name}";
49 | return result;
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/libDokan/VFS/Folders/RestrictedFolderByPID.cs:
--------------------------------------------------------------------------------
1 | using libDokan.Processes;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace libDokan.VFS.Folders
9 | {
10 | public class RestrictedFolderByPID : Folder
11 | {
12 | public RestrictedFolderByPID(string name, Folder? parent, Func isProcessPermitted) : base(name, parent)
13 | {
14 | IsProcessPermitted = isProcessPermitted;
15 | }
16 |
17 | public Func IsProcessPermitted { get; }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/libDokan/VFS/Folders/UnlistedFolder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace libDokan.VFS.Folders
8 | {
9 | public class UnlistedFolder : Folder
10 | {
11 | public UnlistedFolder(string name, Folder? parent) : base(name, parent)
12 | {
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/libDokan/libDokan.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/libGZip/ext/gztool/win-x86_64/gztool-Windows.x86_64.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fiddyschmitt/clonezilla-util/bf65c0522e3035b4b201d065200c211ae734e563/libGZip/ext/gztool/win-x86_64/gztool-Windows.x86_64.exe
--------------------------------------------------------------------------------
/libGZip/libGZip.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 |
7 |
8 |
9 | full
10 | true
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | PreserveNewest
32 |
33 |
34 | Always
35 |
36 |
37 | Never
38 |
39 |
40 | PreserveNewest
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/libPartclone/Cache/IPartcloneCache.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace libPartclone.Cache
6 | {
7 | public interface IPartcloneCache
8 | {
9 | public List? GetPartcloneContentMapping();
10 | public void SetPartcloneContentMapping(List contiguousRanges);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/libPartclone/Extensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Runtime.InteropServices;
7 | using System.Security.Cryptography;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 |
11 | namespace libPartclone
12 | {
13 | public static class Extensions
14 | {
15 | public static byte[] ToByteArray(this BitArray bits)
16 | {
17 | byte[] ret = new byte[(bits.Length - 1) / 8 + 1];
18 | bits.CopyTo(ret, 0);
19 | return ret;
20 | }
21 |
22 | public static string ToHexString(this IEnumerable ba)
23 | {
24 | return BitConverter.ToString(ba.ToArray()).Replace("-", "");
25 | }
26 |
27 | public static string ToString(this IEnumerable list, string linePrefix, string separator)
28 | {
29 | string result = string.Join(separator, list.Select(line => $"{linePrefix}{line}"));
30 | return result;
31 | }
32 |
33 | public static string[] ToLines(this string str)
34 | {
35 | var result = new List();
36 |
37 | using (var sr = new StringReader(str))
38 | {
39 | string? line;
40 | while ((line = sr.ReadLine()) != null)
41 | {
42 | result.Add(line);
43 | }
44 | }
45 |
46 | return [.. result];
47 | }
48 |
49 | public static List> ChunkBy(this List source, int chunkSize)
50 | {
51 | return source
52 | .Select((x, i) => new { Index = i, Value = x })
53 | .GroupBy(x => x.Index / chunkSize)
54 | .Select(x => x.Select(v => v.Value).ToList())
55 | .ToList();
56 | }
57 |
58 | public static IEnumerable GetBits(this byte b)
59 | {
60 | for (int i = 0; i < 8; i++)
61 | {
62 | yield return (b & 0x80) != 0;
63 | b *= 2;
64 | }
65 | }
66 |
67 | public static IEnumerable> GroupAdjacentBy(
68 | this IEnumerable source, Func predicate)
69 | {
70 | using var e = source.GetEnumerator();
71 | if (e.MoveNext())
72 | {
73 | var list = new List { e.Current };
74 | var pred = e.Current;
75 | while (e.MoveNext())
76 | {
77 | if (predicate(pred, e.Current))
78 | {
79 | list.Add(e.Current);
80 | }
81 | else
82 | {
83 | yield return list;
84 | list = [e.Current];
85 | }
86 | pred = e.Current;
87 | }
88 | yield return list;
89 | }
90 | }
91 |
92 | public static IEnumerable Cached(this IEnumerable enumerable)
93 | {
94 | return CachedImpl(enumerable.GetEnumerator(), []);
95 | }
96 |
97 | static IEnumerable CachedImpl(IEnumerator source, List buffer)
98 | {
99 | int pos = 0;
100 | while (true)
101 | {
102 | if (pos == buffer.Count)
103 | if (source.MoveNext())
104 | buffer.Add(source.Current);
105 | else
106 | yield break;
107 | yield return buffer[pos++];
108 | }
109 | }
110 | }
111 |
112 | }
113 |
--------------------------------------------------------------------------------
/libPartclone/Lists/ContiguousRangeComparer.cs:
--------------------------------------------------------------------------------
1 | using libCommon.Lists;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace libPartclone.Lists
9 | {
10 | public class ContiguousRangeComparer : IRangeComparer
11 | {
12 | public int Compare(ContiguousRange range, long value)
13 | {
14 | if (value < range.OutputFileRange.StartByte) return 1;
15 | if (value > range.OutputFileRange.EndByte) return -1;
16 | return 0;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/libPartclone/Metadata/FileSystemInfoV1.cs:
--------------------------------------------------------------------------------
1 | using libCommon;
2 | using System.IO;
3 |
4 | namespace libPartclone.Metadata
5 | {
6 | public class FileSystemInfoV1
7 | {
8 | public uint BlockSize;
9 | public ulong DeviceSizeBytes;
10 | public ulong TotalBlocks;
11 | public ulong UsedBlocks;
12 |
13 | public FileSystemInfoV1()
14 | {
15 |
16 | }
17 |
18 | public FileSystemInfoV1(BinaryReader binaryReader)
19 | {
20 | BlockSize = binaryReader.ReadUInt32();
21 | DeviceSizeBytes = binaryReader.ReadUInt64();
22 | TotalBlocks = binaryReader.ReadUInt64();
23 | UsedBlocks = binaryReader.ReadUInt64();
24 | }
25 |
26 | public override string ToString()
27 | {
28 | var result = $@"
29 | BlockSize: {BlockSize}
30 | DeviceSize: {DeviceSizeBytes:N0} ({DeviceSizeBytes.BytesToString()})
31 | TotalBlocks: {TotalBlocks:N0}
32 | UsedBlocks: {UsedBlocks:N0}
33 | ".Trim();
34 |
35 | return result;
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/libPartclone/Metadata/FileSystemInfoV2.cs:
--------------------------------------------------------------------------------
1 | using libCommon;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace libPartclone.Metadata
10 | {
11 | public class FileSystemInfoV2
12 | {
13 | public string? FileSystemType;
14 |
15 | /// Size of the source device, in bytes
16 | public ulong DeviceSizeBytes;
17 |
18 | /// Number of blocks in the file system
19 | public ulong TotalBlocks;
20 |
21 | /// Number of blocks in use as reported by the file system
22 | public ulong UsedBlocks;
23 |
24 | /// Number of blocks in use within the bitmap
25 | public ulong UsedBitmapBlocks;
26 |
27 | /// Number of bytes in each block
28 | public uint BlockSize;
29 |
30 | public FileSystemInfoV2()
31 | {
32 |
33 | }
34 |
35 | public FileSystemInfoV2(BinaryReader binaryReader)
36 | {
37 | FileSystemType = Encoding.ASCII.GetString(binaryReader.ReadBytes(16)).TrimEnd('\0');
38 |
39 | DeviceSizeBytes = binaryReader.ReadUInt64();
40 | TotalBlocks = binaryReader.ReadUInt64();
41 | UsedBlocks = binaryReader.ReadUInt64();
42 | UsedBitmapBlocks = binaryReader.ReadUInt64();
43 |
44 | BlockSize = binaryReader.ReadUInt32();
45 | }
46 |
47 | public override string ToString()
48 | {
49 | var result = $@"
50 | FileSystemType: {FileSystemType}
51 | DeviceSize: {DeviceSizeBytes:N0} ({DeviceSizeBytes.BytesToString()})
52 | TotalBlocks: {TotalBlocks:N0}
53 | UsedBlocks: {UsedBlocks:N0}
54 | UsedBitmap: {UsedBitmapBlocks:N0}
55 | BlockSize: {BlockSize:N0} ({BlockSize.BytesToString()})
56 | ".Trim();
57 |
58 | return result;
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/libPartclone/Metadata/ImageDescV1.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Text;
5 |
6 | namespace libPartclone.Metadata
7 | {
8 | public class ImageDescV1
9 | {
10 | public ImageHeadV1? ImageHeadV1;
11 | public FileSystemInfoV1? FileSystemInfoV1;
12 | public ImageOptionsV1? ImageOptionsV1;
13 |
14 | public ImageDescV1()
15 | {
16 |
17 | }
18 |
19 | public ImageDescV1(BinaryReader binaryReader)
20 | {
21 | ImageHeadV1 = new ImageHeadV1(binaryReader);
22 | FileSystemInfoV1 = new FileSystemInfoV1(binaryReader);
23 | ImageOptionsV1 = new ImageOptionsV1(binaryReader);
24 | }
25 |
26 | public override string ToString()
27 | {
28 | var imageHeadLines = ImageHeadV1?.ToString().ToLines();
29 | var fileSystemInfoLines = FileSystemInfoV1?.ToString().ToLines();
30 | var imageOptionsLines = ImageOptionsV1?.ToString().ToLines();
31 |
32 | var result = $@"
33 | ImageDescV1
34 |
35 | ImageHeadV1
36 | {imageHeadLines?.ToString(" ", Environment.NewLine)}
37 |
38 | FileSystemInfoV1
39 | {fileSystemInfoLines?.ToString(" ", Environment.NewLine)}
40 |
41 | ImageOptionsV1
42 | {imageOptionsLines?.ToString(" ", Environment.NewLine)}
43 |
44 | ".Trim();
45 |
46 | return result;
47 | }
48 | }
49 | }
50 |
51 |
--------------------------------------------------------------------------------
/libPartclone/Metadata/ImageDescV2.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace libPartclone.Metadata
9 | {
10 | public class ImageDescV2
11 | {
12 | public ImageHeadV2? ImageHeadV2;
13 | public FileSystemInfoV2? FileSystemInfoV2;
14 | public ImageOptionsV2? ImageOptionsV2;
15 | public uint CRC;
16 |
17 | public ImageDescV2()
18 | {
19 |
20 | }
21 |
22 | public ImageDescV2(BinaryReader binaryReader)
23 | {
24 | ImageHeadV2 = new ImageHeadV2(binaryReader); //offset: 0x00
25 | FileSystemInfoV2 = new FileSystemInfoV2(binaryReader); //offset: 0x24
26 | ImageOptionsV2 = new ImageOptionsV2(binaryReader); //offset: 0x58
27 | CRC = binaryReader.ReadUInt32(); //offset: 0x6A
28 | }
29 |
30 | public override string ToString()
31 | {
32 | var imageHeadLines = ImageHeadV2?.ToString().ToLines();
33 | var fileSystemInfoLines = FileSystemInfoV2?.ToString().ToLines();
34 | var imageOptionsLines = ImageOptionsV2?.ToString().ToLines();
35 |
36 | var result = $@"
37 | ImageDescV2
38 |
39 | ImageHeadV2
40 | {imageHeadLines?.ToString(" ", Environment.NewLine)}
41 |
42 | FileSystemInfoV2
43 | {fileSystemInfoLines?.ToString(" ", Environment.NewLine)}
44 |
45 | ImageOptionsV2
46 | {imageOptionsLines?.ToString(" ", Environment.NewLine)}
47 |
48 | CRC
49 | 0x{CRC:X}
50 |
51 |
52 | ".Trim();
53 |
54 | return result;
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/libPartclone/Metadata/ImageHeadV1.cs:
--------------------------------------------------------------------------------
1 | using libCommon;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Text;
6 |
7 | namespace libPartclone.Metadata
8 | {
9 | public class ImageHeadV1
10 | {
11 | //char magic[IMAGE_MAGIC_SIZE];
12 | public string? Magic; //15 bytes
13 |
14 | //char fs[FS_MAGIC_SIZE];
15 | public string? FileSystem; //15 bytes
16 |
17 | //char version[IMAGE_VERSION_SIZE];
18 | public string? ImageVersion; //4 bytes
19 |
20 | //char padding[2];
21 | public string? Padding; //2 bytes
22 |
23 | public ImageHeadV1()
24 | {
25 |
26 | }
27 |
28 | public ImageHeadV1(BinaryReader binaryReader)
29 | {
30 | Magic = Encoding.ASCII.GetString(binaryReader.ReadBytes(15)).TrimEnd('\0');
31 | FileSystem = Encoding.ASCII.GetString(binaryReader.ReadBytes(15)).TrimEnd('\0');
32 | ImageVersion = Encoding.ASCII.GetString(binaryReader.ReadBytes(4)).TrimEnd('\0');
33 | Padding = Encoding.ASCII.GetString(binaryReader.ReadBytes(2)).TrimEnd('\0');
34 | }
35 |
36 | public override string ToString()
37 | {
38 | string[] lines = [
39 | $"Magic: {Magic}",
40 | $"FileSystem: {FileSystem}",
41 | $"ImageVersion: {ImageVersion}",
42 | $"Padding: {Padding}"
43 | ];
44 |
45 | string result = lines.ToString(Environment.NewLine);
46 |
47 | return result;
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/libPartclone/Metadata/ImageHeadV2.cs:
--------------------------------------------------------------------------------
1 | using libCommon;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace libPartclone.Metadata
10 | {
11 | public class ImageHeadV2
12 | {
13 | public string? Magic;
14 |
15 | /// Partclone's version who created the image, ex: "2.61"
16 | public string? PartcloneVersion;
17 |
18 | /// Image's version
19 | public string? ImageVersion;
20 |
21 | /// 0xC0DE = little-endian, 0xDEC0 = big-endian
22 | public bool BigEndian;
23 |
24 | public ImageHeadV2()
25 | {
26 |
27 | }
28 |
29 | public ImageHeadV2(BinaryReader binaryReader)
30 | {
31 | Magic = Encoding.ASCII.GetString(binaryReader.ReadBytes(16)).TrimEnd('\0');
32 | PartcloneVersion = Encoding.ASCII.GetString(binaryReader.ReadBytes(14)).TrimEnd('\0');
33 | ImageVersion = Encoding.ASCII.GetString(binaryReader.ReadBytes(4)).TrimEnd('\0');
34 |
35 | ushort endianess = binaryReader.ReadUInt16();
36 | //ushort endianess = BitConverter.ToUInt16(binaryReader.ReadBytes(2).Reverse().ToArray(), 0);
37 | BigEndian = endianess == 0xC0DE;
38 | }
39 |
40 | public override string ToString()
41 | {
42 | string[] lines = [
43 | $"Magic: {Magic}",
44 | $"PartcloneVersion: {PartcloneVersion}",
45 | $"ImageVersion: {ImageVersion}",
46 | $"Endianess: {(BigEndian ? "Big Endian" : "Little Endian")}"
47 | ];
48 |
49 | string result = lines.ToString(Environment.NewLine);
50 |
51 | return result;
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/libPartclone/Metadata/ImageOptionsV1.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 |
3 | namespace libPartclone.Metadata
4 | {
5 | public class ImageOptionsV1
6 | {
7 | public byte[] Buff = new byte[4096];
8 |
9 | public ImageOptionsV1()
10 | {
11 |
12 | }
13 |
14 | public ImageOptionsV1(BinaryReader binaryReader)
15 | {
16 | Buff = binaryReader.ReadBytes(Buff.Length);
17 | }
18 |
19 | public override string ToString()
20 | {
21 | var result = $@"
22 | Buff Length: {Buff.Length}
23 | ".Trim();
24 |
25 | return result;
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/libPartclone/Metadata/ImageOptionsV2.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace libPartclone.Metadata
9 | {
10 | public class ImageOptionsV2
11 | {
12 | /// Number of bytes used by this struct
13 | public uint FeatureSize;
14 |
15 | /// version of the image
16 | public ushort ImageVersion;
17 |
18 | /// partclone's compilation architecture: 32 bits or 64 bits
19 | public ushort CpuBits;
20 |
21 | /// checksum algorithm used (see checksum_mode_enum)
22 | public CrcModeEnum ChecksumMode;
23 |
24 | /// Size of one checksum, in bytes. 0 when NONE, 4 with CRC32, etc.
25 | public ushort ChecksumSize;
26 |
27 | /// How many consecutive blocks are checksumed together.
28 | public uint BlocksPerChecksum;
29 |
30 | /// Reseed the checksum after each write (1 = yes; 0 = no)
31 | public byte ReseedChecksum;
32 |
33 | /// Kind of bitmap stored in the image (see bitmap_mode_enum)
34 | public BitmapMode BitmapMode;
35 |
36 | public ImageOptionsV2()
37 | {
38 |
39 | }
40 |
41 | public ImageOptionsV2(BinaryReader binaryReader)
42 | {
43 | FeatureSize = binaryReader.ReadUInt32();
44 |
45 | ImageVersion = binaryReader.ReadUInt16();
46 | CpuBits = binaryReader.ReadUInt16();
47 | ChecksumMode = (CrcModeEnum)binaryReader.ReadUInt16();
48 | ChecksumSize = binaryReader.ReadUInt16();
49 |
50 | BlocksPerChecksum = binaryReader.ReadUInt32();
51 |
52 | ReseedChecksum = binaryReader.ReadByte();
53 | BitmapMode = (BitmapMode)binaryReader.ReadByte();
54 | }
55 |
56 | public override string ToString()
57 | {
58 | var result = $@"
59 | FeatureSize: {FeatureSize}
60 | ImageVersion: {ImageVersion}
61 | CpuBits: {CpuBits}
62 | ChecksumMode: {ChecksumMode}
63 | ChecksumSize: {ChecksumSize}
64 | BlocksPerChecksum: {BlocksPerChecksum}
65 | ReseedChecksum: {ReseedChecksum}
66 | BitmapMode: {BitmapMode}
67 |
68 | ".Trim();
69 |
70 | return result;
71 | }
72 | }
73 |
74 | public enum BitmapMode
75 | {
76 | BM_NONE = 0x00,
77 | BM_BIT = 0x01,
78 | BM_BYTE = 0x08,
79 | }
80 |
81 | public enum CrcModeEnum
82 | {
83 | CSM_NONE = 0x00,
84 | CSM_CRC32 = 0x20,
85 | CSM_CRC32_0001 = 0xFF,
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/libPartclone/libPartclone.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | 08e71b31-a475-4367-8c34-a03aac4fa2f7
7 |
8 |
9 |
10 | full
11 | true
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/libTrainCompress/Compressors/Compressor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace libTrainCompress.Compressors
8 | {
9 | public abstract class Compressor
10 | {
11 | public string CompressionFormat;
12 | public abstract Stream GetCompressor(Stream streamToWriteTo);
13 |
14 | public abstract Stream GetDecompressor(Stream streamToReadFrom);
15 |
16 | public Compressor(string compressionFormat)
17 | {
18 | CompressionFormat = compressionFormat;
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/libTrainCompress/Compressors/gzCompressor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO.Compression;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace libTrainCompress.Compressors
9 | {
10 | public class gzCompressor : Compressor
11 | {
12 | public gzCompressor() : base("gz")
13 | {
14 | }
15 |
16 | public override Stream GetCompressor(Stream streamToWriteTo)
17 | {
18 | var result = new GZipStream(streamToWriteTo, CompressionMode.Compress, true);
19 | return result;
20 | }
21 |
22 |
23 | public override Stream GetDecompressor(Stream streamToReadFrom)
24 | {
25 | var result = new GZipStream(streamToReadFrom, CompressionMode.Decompress, true);
26 | return result;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/libTrainCompress/Compressors/xzCompressor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace libTrainCompress.Compressors
8 | {
9 | public class xzCompressor : Compressor
10 | {
11 | public xzCompressor() : base("xz")
12 | {
13 | }
14 |
15 | public override Stream GetCompressor(Stream streamToWriteTo)
16 | {
17 | //Joveler.Compression.XZ
18 | /*
19 |
20 | var compressOptions = new XZCompressOptions()
21 | {
22 | LeaveOpen = true
23 | };
24 |
25 | var threadedCompressOptions = new XZThreadedCompressOptions()
26 | {
27 | Threads = Environment.ProcessorCount
28 | };
29 |
30 |
31 | XZInit.GlobalInit();
32 |
33 | var verInst = XZInit.Version();
34 | Console.WriteLine($"liblzma Version (Version) = {verInst}");
35 |
36 | var verStr = XZInit.VersionString();
37 | Console.WriteLine($"liblzma Version (String) = {verStr}");
38 |
39 |
40 | var result = new Joveler.Compression.XZ.XZStream(streamToWriteTo, compressOptions, threadedCompressOptions);
41 | return result;
42 | */
43 |
44 | var result = new XZ.NET.XZOutputStream(streamToWriteTo, Environment.ProcessorCount, 0, true);
45 | return result;
46 | }
47 |
48 | public override Stream GetDecompressor(Stream streamToReadFrom)
49 | {
50 | var result = new XZ.NET.XZInputStream(streamToReadFrom, true);
51 |
52 | return result;
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/libTrainCompress/Compressors/zstdCompressor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace libTrainCompress.Compressors
8 | {
9 | public class zstdCompressor : Compressor
10 | {
11 | public zstdCompressor() : base("zstd")
12 | {
13 | }
14 |
15 | public override Stream GetCompressor(Stream streamToWriteTo)
16 | {
17 | var result = new ZstdNet.CompressionStream(streamToWriteTo);
18 | return result;
19 | }
20 |
21 | public override Stream GetDecompressor(Stream streamToReadFrom)
22 | {
23 | var result = new ZstdNet.DecompressionStream(streamToReadFrom);
24 | return result;
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/libTrainCompress/GlobalSuppressions.cs:
--------------------------------------------------------------------------------
1 | // This file is used by Code Analysis to maintain SuppressMessage
2 | // attributes that are applied to this project.
3 | // Project-level suppressions either have no target or are given
4 | // a specific target and scoped to a namespace, type, member, etc.
5 |
6 | using System.Diagnostics.CodeAnalysis;
7 |
8 | [assembly: SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "", Scope = "module")]
9 | [assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "", Scope = "module")]
10 | [assembly: SuppressMessage("Maintainability", "CA1513:Use ObjectDisposedException throw helper", Justification = "", Scope = "module")]
11 |
--------------------------------------------------------------------------------
/libTrainCompress/Lists/CarriageComparer.cs:
--------------------------------------------------------------------------------
1 | using libCommon.Lists;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace libTrainCompress.Lists
9 | {
10 | public class CarriageComparer : IRangeComparer
11 | {
12 | public int Compare(Carriage range, long value)
13 | {
14 | if (value < range.UncompressedStartByte) return 1;
15 | if (value >= range.UncompressedEndByte) return -1;
16 | return 0;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/libTrainCompress/Streams/SubStream.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace libTrainCompress.Streams
8 | {
9 | public class SubStream : Stream
10 | {
11 | readonly Stream vector;
12 | private readonly long offset;
13 | private readonly long length;
14 | private long position = 0;
15 |
16 | public SubStream(Stream vector, long offset, long length)
17 | {
18 | if (length < 1) throw new ArgumentException("Length must be greater than zero.");
19 |
20 | this.vector = vector;
21 | this.offset = offset;
22 | this.length = length;
23 |
24 | vector.Seek(offset, SeekOrigin.Begin);
25 | }
26 |
27 | public override int Read(byte[] buffer, int offset, int count)
28 | {
29 | CheckDisposed();
30 | long remaining = length - position;
31 | if (remaining <= 0) return 0;
32 | if (remaining < count) count = (int)remaining;
33 | int read = vector.Read(buffer, offset, count);
34 | position += read;
35 | return read;
36 | }
37 |
38 | private void CheckDisposed()
39 | {
40 | if (vector == null) throw new ObjectDisposedException(GetType().Name);
41 | }
42 |
43 | public override long Seek(long offset, SeekOrigin origin)
44 | {
45 | long pos = position;
46 |
47 | if (origin == SeekOrigin.Begin)
48 | pos = offset;
49 | else if (origin == SeekOrigin.End)
50 | pos = length + offset;
51 | else if (origin == SeekOrigin.Current)
52 | pos += offset;
53 |
54 | if (pos < 0) pos = 0;
55 | else if (pos >= length) pos = length - 1;
56 |
57 | position = vector.Seek(this.offset + pos, SeekOrigin.Begin) - this.offset;
58 |
59 | return pos;
60 | }
61 |
62 | public override bool CanRead => true;
63 |
64 | public override bool CanSeek => true;
65 |
66 | public override bool CanWrite => false;
67 |
68 | public override long Length => length;
69 |
70 | public override long Position { get => position; set { position = this.Seek(value, SeekOrigin.Begin); } }
71 |
72 | public override void Flush()
73 | {
74 | throw new NotImplementedException();
75 | }
76 |
77 | public override void SetLength(long value)
78 | {
79 | throw new NotImplementedException();
80 | }
81 |
82 | public override void Write(byte[] buffer, int offset, int count)
83 | {
84 | throw new NotImplementedException();
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/libTrainCompress/libTrainCompress.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 | AnyCPU
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------