From ff9576e30a45a8f06a6e4821c5fd3e4dbe4fca20 Mon Sep 17 00:00:00 2001 From: Hawkynt Date: Thu, 1 Oct 2026 13:50:05 +0200 Subject: [PATCH 1/7] + Reiser4 multi-level writing and metadata-preserving rebuilds Wide namespaces outgrew the single-leaf writer, and rebuilds discarded native object metadata and directory structure. Pack bounded leaves and twig extents beneath as many pointer levels as needed, traverse the complete native tree, and carry object identity, Unix fields and opaque stat-data tails through staged edits, defrag, shrink and rebuild. Reserve every tree node during wipe and cache traversal for large streaming rebuilds. Sourcing rung 2: no licence-compatible managed Reiser4 implementation was identified. Derived node40/stat40/cde40/extent40 layout and synthetic tests are recorded in docs/REISER4-NATIVE-PROFILE.md; GPL reiser4progs 1.2.2 is an external oracle, not copied or converted source. Validation: Reiser4-focused run passed 95 tests with one obsolete sentinel skipped. fsck.reiser4 accepted nested 1-, 100- and 5000-file images; external mutation tests check Unix metadata changes, add/remove and defrag. --- .../ConversionMatrix/ConversionMatrixTests.cs | 15 +- Compression.Tests/ExternalFsInteropTests.cs | 16 +- .../Reiser4/Reiser4MutationExternalTests.cs | 35 ++ .../Reiser4/Reiser4NativeTreeTests.cs | 218 +++++++++- Compression.Tests/Reiser4/Reiser4Tests.cs | 8 +- .../Reiser4FormatDescriptor.cs | 331 +++++++++++--- .../FileSystem.Reiser4/Reiser4Reader.cs | 336 ++++++++++----- .../FileSystem.Reiser4/Reiser4Tree.cs | 404 ++++++++++++------ .../FileSystem.Reiser4/Reiser4Writer.cs | 154 +++---- Hawkynt.FileFormats.FileSystems/README.md | 2 +- docs/REISER4-NATIVE-PROFILE.md | 86 ++++ 11 files changed, 1165 insertions(+), 440 deletions(-) create mode 100644 docs/REISER4-NATIVE-PROFILE.md diff --git a/Compression.Tests/ConversionMatrix/ConversionMatrixTests.cs b/Compression.Tests/ConversionMatrix/ConversionMatrixTests.cs index 88a6b685a..5978a0c11 100644 --- a/Compression.Tests/ConversionMatrix/ConversionMatrixTests.cs +++ b/Compression.Tests/ConversionMatrix/ConversionMatrixTests.cs @@ -170,20 +170,7 @@ public static IEnumerable Grid() { // SOURCE→TARGET pair-specific gaps (target works from most sources but fails // from one). Keyed "Source|Target", case-insensitive. - private static readonly Dictionary KnownGapPairs = - new(StringComparer.OrdinalIgnoreCase) { - // The native Reiser4 profile writes regular files in the root directory only. - // Sources whose matrix payload carries SUB/INNER.TXT are refused rather than - // written as one entry whose name contains a slash; flat sources convert. - { "Zip|Reiser4", ReiserRootOnly }, - { "Tar|Reiser4", ReiserRootOnly }, - { "SevenZip|Reiser4", ReiserRootOnly }, - { "Cpio|Reiser4", ReiserRootOnly }, - { "Fat|Reiser4", ReiserRootOnly }, - }; - - private const string ReiserRootOnly = - "[name/charset/size constraint] Reiser4 profile is root-directory only; the nested SUB/INNER.TXT payload is refused"; + private static readonly Dictionary KnownGapPairs = new(StringComparer.OrdinalIgnoreCase); /// /// Returns the documented gap reason for a pair if it is a known, quarantined diff --git a/Compression.Tests/ExternalFsInteropTests.cs b/Compression.Tests/ExternalFsInteropTests.cs index 3d78983d5..01ed2c2a2 100644 --- a/Compression.Tests/ExternalFsInteropTests.cs +++ b/Compression.Tests/ExternalFsInteropTests.cs @@ -2121,13 +2121,15 @@ public void Reiser4_LinuxMkfsOutput_ReadByOurReader() { Directory.CreateDirectory(extractDir); using (var s2 = File.OpenRead(imgPath)) descriptor.Extract(s2, extractDir, null, null); - var metaPath = Path.Combine(extractDir, "metadata.ini"); - Assert.That(File.Exists(metaPath), $"metadata.ini missing in {extractDir}"); - var meta = File.ReadAllText(metaPath); - Assert.That(meta, Does.Contain("parse_status=ok"), - $"Our reader failed to parse the Reiser4 master superblock:\n{meta}"); - Assert.That(meta, Does.Match(@"blocksize=\d+"), - $"metadata.ini should report a non-empty blocksize:\n{meta}"); + using var native = new Reiser4Reader(stream); + Assert.Multiple(() => { + Assert.That(native.Valid, Is.True); + Assert.That(native.NativeTreeValid, Is.True); + Assert.That(native.BlockSize, Is.EqualTo(4096)); + Assert.That(native.RootMetadata, Is.Not.Null); + Assert.That(entries, Is.Empty, "a freshly formatted native namespace contains no user members"); + Assert.That(Directory.GetFiles(extractDir), Is.Empty); + }); } [Test] diff --git a/Compression.Tests/Reiser4/Reiser4MutationExternalTests.cs b/Compression.Tests/Reiser4/Reiser4MutationExternalTests.cs index 1b386159b..b94379f8c 100644 --- a/Compression.Tests/Reiser4/Reiser4MutationExternalTests.cs +++ b/Compression.Tests/Reiser4/Reiser4MutationExternalTests.cs @@ -122,4 +122,39 @@ public void GivenAnExistingVolume_WhenAddingReplacingAndRemovingByRebuild_ThenRe Assert.That(reader.Extract(reader.Entries.Single()), Is.EqualTo(replacement)); }); } + + [Test, Category("HappyPath")] + public void MultiLevelNestedTree_RemainsConsistentAfterMetadataAndRebuildEdits() { + RequireReiser4Progs(); + var writer = new Reiser4Writer { Label = "nested" }; + for (var i = 0; i < 5_000; ++i) + writer.AddFile($"wide/file-{i:D5}.bin", i == 0 ? Payload(123, 20) : []); + writer.AddDirectory("empty/child"); + using var image = new MemoryStream(); + writer.Write(image); + this.AssertReiser4ProgsAccept(image.ToArray(), "wide", "empty"); + Reiser4Reader.FileMetadata before; + using (var reader = new Reiser4Reader(image)) + before = reader.Entries.Single(static entry => entry.Name == "wide/file-00000.bin").Metadata!; + var descriptor = new Reiser4FormatDescriptor(); + descriptor.UpdateMetadata(image, "wide/file-00000.bin", before with { + Mode = 0x8180, UserId = 300, GroupId = 400, + ModifiedTime = 1234567, AccessedTime = 7654321, ChangedTime = 2345678, + }); + descriptor.Add(image, [ArchiveInputInfo.InMemory("wide/added.bin", Payload(9_137, 21))]); + descriptor.Remove(image, ["empty"]); + descriptor.Defragment(image); + this.AssertReiser4ProgsAccept(image.ToArray(), "wide"); + using var result = new Reiser4Reader(image); + var kept = result.Entries.Single(static entry => entry.Name == "wide/file-00000.bin"); + Assert.Multiple(() => { + Assert.That(result.NativeTreeValid, Is.True); + Assert.That(kept.Metadata!.ObjectId, Is.EqualTo(before.ObjectId)); + Assert.That(kept.Metadata.Mode, Is.EqualTo(0x8180)); + Assert.That(kept.Metadata.UserId, Is.EqualTo(300)); + Assert.That(kept.Metadata.GroupId, Is.EqualTo(400)); + Assert.That(result.Extract(kept), Is.EqualTo(Payload(123, 20))); + Assert.That(result.Entries.Any(static entry => entry.Name == "empty" || entry.Name.StartsWith("empty/")), Is.False); + }); + } } diff --git a/Compression.Tests/Reiser4/Reiser4NativeTreeTests.cs b/Compression.Tests/Reiser4/Reiser4NativeTreeTests.cs index 8ad9623ef..1faccd278 100644 --- a/Compression.Tests/Reiser4/Reiser4NativeTreeTests.cs +++ b/Compression.Tests/Reiser4/Reiser4NativeTreeTests.cs @@ -1,3 +1,4 @@ +using System.Buffers.Binary; using Compression.Registry; using FileSystem.Reiser4; @@ -131,8 +132,7 @@ public void Descriptor_DefaultMutation_RebuildsAndRemainsReadable() { Assert.That(reader.Extract(reader.Entries.Single(static entry => entry.Name == "added.bin")), Is.EqualTo(added)); }); } - [TestCase("sub/inner.dat", TestName = "GivenANestedName_WhenAddingToTheWriter_ThenItIsRefused")] - [TestCase("sub\\inner.dat", TestName = "GivenABackslashNestedName_WhenAddingToTheWriter_ThenItIsRefused")] + [TestCase(".", TestName = "GivenTheDotName_WhenAddingToTheWriter_ThenItIsRefused")] [TestCase("..", TestName = "GivenTheDotDotName_WhenAddingToTheWriter_ThenItIsRefused")] [Category("Exceptional")] @@ -145,6 +145,17 @@ public void Writer_RefusesNamesTheRootDirectoryCannotHold(string name) { }); } + [Test, Category("Exceptional")] + public void Writer_RefusesPathsBeyondTheReaderDepthLimit() { + var name = string.Join('/', Enumerable.Repeat("directory", 130)); + var writer = new Reiser4Writer(); + Assert.Multiple(() => { + Assert.That(() => writer.AddFile(name, [1]), Throws.TypeOf()); + Assert.That(() => writer.AddDirectory(name), Throws.TypeOf()); + Assert.That(new Reiser4FormatDescriptor().CanAccept(ArchiveInputInfo.InMemory(name, [1]), out _), Is.False); + }); + } + [Test, Category("Exceptional")] public void GivenTheSameNameTwice_WhenAddingToTheWriter_ThenTheSecondIsRefused() { var writer = new Reiser4Writer(); @@ -153,11 +164,11 @@ public void GivenTheSameNameTwice_WhenAddingToTheWriter_ThenTheSecondIsRefused() } [Test, Category("EquivalenceClass")] - public void GivenADirectoryInput_WhenAskingTheConstraints_ThenItIsRefusedWithAReason() { + public void GivenADirectoryInput_WhenAskingTheConstraints_ThenItIsAccepted() { var accepted = new Reiser4FormatDescriptor().CanAccept(new ArchiveInputInfo("sub", "sub", IsDirectory: true), out var reason); Assert.Multiple(() => { - Assert.That(accepted, Is.False); - Assert.That(reason, Does.Contain("root directory")); + Assert.That(accepted, Is.True); + Assert.That(reason, Is.Null); }); } @@ -187,4 +198,201 @@ public void GivenFiles_WhenWriting_ThenExtentsSitInTheTwigAndNotInTheLeaf() { Assert.That(leaf.Plugins, Does.Not.Contain((ushort)5), "a leaf never holds an extent"); }); } + + + [Test, Category("RoundTrip")] + public void Writer_EmitsNestedDirectoriesInTheNativeNamespace() { + var payload = Payload(4_137, 8); + var writer = new Reiser4Writer(); + writer.AddFile("one/two/three.bin", payload); + using var image = new MemoryStream(writer.Build(), writable: false); + using var reader = new Reiser4Reader(image); + + Assert.Multiple(() => { + Assert.That(reader.Entries.Where(static entry => entry.IsDirectory).Select(static entry => entry.Name), + Is.EquivalentTo(new[] { "one", "one/two" })); + Assert.That(reader.Entries.Single(static entry => entry.Name == "one/two/three.bin").Size, + Is.EqualTo(payload.Length)); + Assert.That(reader.Extract(reader.Entries.Single(static entry => entry.Name == "one/two/three.bin")), + Is.EqualTo(payload)); + }); + } + + [Test, Category("RoundTrip")] + public void Reader_FollowsMultipleInternalNodeLevels() { + var payload = Payload(2_033, 9); + var writer = new Reiser4Writer(); + writer.AddFile("deep-tree.bin", payload); + var image = writer.Build(); + + const int blockSize = Reiser4Writer.BlockSize; + const int rootBlock = 23; + const int intermediateBlock = 4095; + image.AsSpan(rootBlock * blockSize, blockSize).CopyTo(image.AsSpan(intermediateBlock * blockSize, blockSize)); + BinaryPrimitives.WriteUInt64LittleEndian(image.AsSpan(rootBlock * blockSize + Reiser4Tree.NodeHeaderBytes, 8), + intermediateBlock); + BinaryPrimitives.WriteUInt16LittleEndian(image.AsSpan(rootBlock * blockSize + 2), 1); + BinaryPrimitives.WriteUInt16LittleEndian(image.AsSpan(rootBlock * blockSize + 4), + blockSize - Reiser4Tree.ItemHeaderBytes - Reiser4Tree.NodeHeaderBytes - 8); + BinaryPrimitives.WriteUInt16LittleEndian(image.AsSpan(rootBlock * blockSize + 6), Reiser4Tree.NodeHeaderBytes + 8); + image[rootBlock * blockSize + 26] = 3; + BinaryPrimitives.WriteUInt16LittleEndian(image.AsSpan(17 * blockSize + 68, 2), 3); + + using var stream = new MemoryStream(image, writable: false); + using var reader = new Reiser4Reader(stream); + Assert.That(reader.Extract(reader.Entries.Single()), Is.EqualTo(payload)); + } + + [Test, Category("RoundTrip")] + public void Rebuild_RetainsStatExtensionsEmptyDirectoriesAndVolumeIdentity() { + var writer = new Reiser4Writer { Label = "metadata", Uuid = Enumerable.Range(0, 16).Select(static i => (byte)i).ToArray() }; + writer.AddFile("parent/file.bin", Payload(100, 10)); + using var source = new MemoryStream(writer.Build()); + using var initial = new Reiser4Reader(source); + var metadata = initial.Entries.Single(static e => !e.IsDirectory).Metadata!; + var raw = metadata.RawStatData.Concat(new byte[] { 0xDE, 0xAD, 0xBE, 0xEF }).ToArray(); + BinaryPrimitives.WriteUInt32LittleEndian(raw.AsSpan(16), 1234); + BinaryPrimitives.WriteUInt32LittleEndian(raw.AsSpan(20), 5678); + var retained = metadata with { UserId = 1234, GroupId = 5678, RawStatData = raw }; + var decorated = new Reiser4Writer { Label = writer.Label, Uuid = writer.Uuid }; + decorated.AddFile("parent/file.bin", Payload(100, 10), retained); + decorated.AddDirectory("empty/child"); + using var image = new MemoryStream(); + decorated.Write(image); + var descriptor = new Reiser4FormatDescriptor(); + descriptor.Add(image, [ArchiveInputInfo.InMemory("added.bin", Payload(150, 11))]); + descriptor.Defragment(image); + using var result = new Reiser4Reader(image); + Assert.Multiple(() => { + Assert.That(result.NativeTreeValid, Is.True); + Assert.That(result.Label, Is.EqualTo("metadata")); + Assert.That(result.UuidHex, Is.EqualTo(Convert.ToHexString(writer.Uuid!))); + Assert.That(result.Entries.Single(static e => e.Name == "parent/file.bin").Metadata!.RawStatData, + Is.EqualTo(raw)); + Assert.That(result.Entries.Single(static e => e.Name == "empty/child").IsDirectory, Is.True); + }); + } + + [Test] + public void UnsupportedTree_RebuildDoesNotOverwriteOriginal() { + var imageBytes = BuildWithoutLegacyDirectory(("file.bin", Payload(100, 12))); + BinaryPrimitives.WriteUInt64LittleEndian(imageBytes.AsSpan(23 * Reiser4Writer.BlockSize + 28), 23); + using var image = new MemoryStream(imageBytes.ToArray()); + var descriptor = new Reiser4FormatDescriptor(); + Assert.Throws(() => descriptor.Add(image, + [ArchiveInputInfo.InMemory("added.bin", new byte[] { 1 })])); + Assert.That(image.ToArray(), Is.EqualTo(imageBytes)); + using var reader = new Reiser4Reader(image); + Assert.That(reader.NativeTreeValid, Is.False); + Assert.That(reader.Entries, Is.Empty); + } + + [Test] + public void Writer_RejectsFileDirectoryCollisions() { + var writer = new Reiser4Writer(); + writer.AddFile("collision", new byte[] { 1 }); + writer.AddFile("collision/child", new byte[] { 2 }); + Assert.Throws(() => writer.Build()); + } + + [Test, Category("RoundTrip")] + public void Writer_PacksWideDirectoryAcrossLeavesAndInternalLevels() { + const int count = 5_000; + var writer = new Reiser4Writer(); + for (var i = 0; i < count; ++i) + writer.AddFile($"wide/file-{i:D5}.bin", i % 1000 == 0 ? Payload(17, i) : []); + using var image = new MemoryStream(writer.Build()); + using var reader = new Reiser4Reader(image); + var byName = reader.Entries.ToDictionary(static entry => entry.Name); + Assert.Multiple(() => { + Assert.That(reader.NativeTreeValid, Is.True); + Assert.That(reader.NativeNodeBlocks.Count, Is.GreaterThan(89)); + Assert.That(byName.Count, Is.EqualTo(count + 1)); + for (var i = 0; i < count; i += 1000) + Assert.That(reader.Extract(byName[$"wide/file-{i:D5}.bin"]), Is.EqualTo(Payload(17, i))); + var format = image.ToArray().AsSpan(17 * Reiser4Writer.BlockSize); + Assert.That(BinaryPrimitives.ReadUInt16LittleEndian(format[68..]), Is.GreaterThanOrEqualTo(3)); + }); + var snapshot = image.ToArray(); + var nodes = reader.NativeNodeBlocks.ToDictionary(static block => block, + block => snapshot.AsSpan(checked((int)block * Reiser4Writer.BlockSize), Reiser4Writer.BlockSize).ToArray()); + new Reiser4FormatDescriptor().WipeUnusedSpace(image); + var wiped = image.ToArray(); + foreach (var (block, node) in nodes) + Assert.That(wiped.AsSpan(checked((int)block * Reiser4Writer.BlockSize), Reiser4Writer.BlockSize).ToArray(), + Is.EqualTo(node)); + } + + [Test, Category("RoundTrip")] + public void Writer_RoundTripsMoreThan1024Directories() { + const int count = 1_100; + var writer = new Reiser4Writer(); + for (var i = 0; i < count; ++i) + writer.AddDirectory($"directory-{i:D4}"); + using var image = new MemoryStream(writer.Build()); + using var reader = new Reiser4Reader(image); + Assert.Multiple(() => { + Assert.That(reader.NativeTreeValid, Is.True); + Assert.That(reader.Entries.Count, Is.EqualTo(count)); + Assert.That(reader.Entries.All(static entry => entry.IsDirectory), Is.True); + Assert.That(reader.Entries.Select(static entry => entry.Name).Distinct().Count(), Is.EqualTo(count)); + }); + } + + [Test, Category("RoundTrip")] + public void MetadataUpdate_AppliesTypedFieldsAndKeepsIdentityAcrossReplacement() { + var writer = new Reiser4Writer(); + writer.AddFile("directory/file.bin", Payload(81, 13)); + using var image = new MemoryStream(); + writer.Write(image); + Reiser4Reader.FileMetadata before; + using (var reader = new Reiser4Reader(image)) + before = reader.Entries.Single(static entry => !entry.IsDirectory).Metadata!; + var updated = before with { + Mode = 0x8180, UserId = 300, GroupId = 400, + ModifiedTime = 1234567, AccessedTime = 7654321, ChangedTime = 2345678, + }; + var descriptor = new Reiser4FormatDescriptor(); + descriptor.UpdateMetadata(image, "directory/file.bin", updated); + descriptor.Add(image, [ArchiveInputInfo.InMemory("directory/file.bin", Payload(9_137, 14))]); + using var result = new Reiser4Reader(image); + var entry = result.Entries.Single(static entry => !entry.IsDirectory); + Assert.Multiple(() => { + Assert.That(result.NativeTreeValid, Is.True); + Assert.That(entry.Metadata!.ObjectId, Is.EqualTo(before.ObjectId)); + Assert.That(entry.Metadata.StatLocality, Is.EqualTo(before.StatLocality)); + Assert.That(entry.Metadata.Mode, Is.EqualTo(updated.Mode)); + Assert.That(entry.Metadata.UserId, Is.EqualTo(updated.UserId)); + Assert.That(entry.Metadata.GroupId, Is.EqualTo(updated.GroupId)); + Assert.That(entry.Metadata.ModifiedTime, Is.EqualTo(updated.ModifiedTime)); + Assert.That(entry.Metadata.AccessedTime, Is.EqualTo(updated.AccessedTime)); + Assert.That(entry.Metadata.ChangedTime, Is.EqualTo(updated.ChangedTime)); + Assert.That(entry.Metadata!.Size, Is.EqualTo(9_137)); + Assert.That(result.Extract(entry), Is.EqualTo(Payload(9_137, 14))); + }); + } + + [Test, Category("RoundTrip")] + public void Writer_PlacesExtentsInTwigNodes() { + var writer = new Reiser4Writer(); + writer.AddFile("extent.bin", Payload(9_137, 15)); + using var image = new MemoryStream(writer.Build()); + using var reader = new Reiser4Reader(image); + var bytes = image.ToArray(); + var extentCount = 0; + foreach (var block in reader.NativeNodeBlocks) { + var node = bytes.AsSpan(checked((int)block * Reiser4Writer.BlockSize), Reiser4Writer.BlockSize); + var count = BinaryPrimitives.ReadUInt16LittleEndian(node[2..]); + for (var i = 0; i < count; ++i) { + var header = Reiser4Writer.BlockSize - (i + 1) * Reiser4Tree.ItemHeaderBytes; + if (BinaryPrimitives.ReadUInt16LittleEndian(node[(header + 36)..]) != 5) continue; + ++extentCount; + Assert.That(node[26], Is.EqualTo(2)); + } + } + Assert.That(extentCount, Is.GreaterThan(0)); + Assert.That(reader.NativeTreeValid, Is.True); + Assert.That(reader.Extract(reader.Entries.Single()), Is.EqualTo(Payload(9_137, 15))); + } + } diff --git a/Compression.Tests/Reiser4/Reiser4Tests.cs b/Compression.Tests/Reiser4/Reiser4Tests.cs index 28a4de911..f05ce320a 100644 --- a/Compression.Tests/Reiser4/Reiser4Tests.cs +++ b/Compression.Tests/Reiser4/Reiser4Tests.cs @@ -246,12 +246,14 @@ public void Writer_RoundTrip_OurReader() { Assert.That(sb.TreeHeight, Is.EqualTo(2)); Assert.That(sb.Policy, Is.EqualTo(2)); - // Descriptor List() should expose the standard surface from our own image. + // A valid empty namespace must not turn diagnostic superblock views into live members. using var ms = new MemoryStream(img); var d = new FileSystem.Reiser4.Reiser4FormatDescriptor(); var entries = d.List(ms, null); - Assert.That(entries.Select(e => e.Name), Does.Contain("master_superblock.bin")); - Assert.That(entries.Select(e => e.Name), Does.Contain("format40_superblock.bin")); + Assert.That(entries, Is.Empty); + using var reader = new FileSystem.Reiser4.Reiser4Reader(ms); + Assert.That(reader.NativeTreeValid, Is.True); + Assert.That(reader.RootMetadata, Is.Not.Null); } [Test, Category("HappyPath")] diff --git a/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Reiser4/Reiser4FormatDescriptor.cs b/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Reiser4/Reiser4FormatDescriptor.cs index 72e72994b..246c121cf 100644 --- a/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Reiser4/Reiser4FormatDescriptor.cs +++ b/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Reiser4/Reiser4FormatDescriptor.cs @@ -61,7 +61,7 @@ public sealed class Reiser4FormatDescriptor : IFormatDescriptor, IArchiveFormatO /// public FormatCapabilities Capabilities => FormatCapabilities.CanList | FormatCapabilities.CanExtract | FormatCapabilities.CanTest | - FormatCapabilities.CanCreate | FormatCapabilities.CanModify; + FormatCapabilities.CanCreate | FormatCapabilities.CanModify | FormatCapabilities.SupportsDirectories; /// /// Gets the default extension. /// @@ -102,7 +102,7 @@ public sealed class Reiser4FormatDescriptor : IFormatDescriptor, IArchiveFormatO /// /// Gets the description. /// - public string Description => "Reiser4 filesystem image — native single-leaf file profile with streaming create, rebuild mutation, defrag and wipe."; + public string Description => "Reiser4 filesystem image — native node40 traversal and multi-leaf writing with nested directories and metadata-preserving rebuilds."; /// /// Lists the entries in the supplied container. @@ -136,10 +136,15 @@ public List List(Stream stream, string? password) { var idx = 0; if (payload.Count > 0) { foreach (var e in payload) - entries.Add(new ArchiveEntryInfo(idx++, e.Name, e.Size, e.Size, "stored", false, false, null)); + entries.Add(new ArchiveEntryInfo(idx++, e.Name, e.Size, e.Size, "stored", e.IsDirectory, false, + e.Metadata?.LastModified)); return entries; } + if (stream.CanSeek) stream.Position = 0; + using (var native = new Reiser4Reader(stream)) + if (native.NativeTreeValid) return entries; + entries.Add(new ArchiveEntryInfo(idx++, "FULL.reiser4", image.LongLength, image.LongLength, "stored", false, false, null)); entries.Add(new ArchiveEntryInfo(idx++, "metadata.ini", 0, 0, "stored", false, false, null)); if (sb.Valid) @@ -184,11 +189,12 @@ public void Extract(Stream stream, string outputDir, string? password, string[]? // A volume that carries files extracts exactly those, mirroring List. if (stream.CanSeek) stream.Position = 0; using (var reader = new Reiser4Reader(stream)) { - if (reader.Entries.Count > 0) { + if (reader.NativeTreeValid || reader.Entries.Count > 0) { foreach (var e in reader.Entries) { if (files is { Length: > 0 } && !MatchesFilter(e.Name, files)) continue; var target = Path.Combine(outputDir, e.Name.Replace('/', Path.DirectorySeparatorChar)); Directory.CreateDirectory(Path.GetDirectoryName(target) ?? outputDir); + if (e.IsDirectory) { Directory.CreateDirectory(target); continue; } using var output = File.Create(target); reader.ExtractTo(e, output); } @@ -232,17 +238,24 @@ private static byte[] BuildMetadata(Reiser4MasterSb sb) { } // ── IArchiveCreatable ──────────────────────────────────────────────── - // The reserved blocks are byte-exact mkfs.reiser4 captures describing an empty - // storage tree; growing that tree (extent40 item bodies keyed by file offset, - // cde40 directory units) is not reproduced here. Files go in the workbench-layout - // payload area past those blocks, with the block-allocator bitmap and - // sb_free_blocks kept consistent so the volume stays internally coherent. + // A captured mkfs prefix supplies the format defaults; native stat40, cde40 + // and extent40 items describe the supported namespace across native nodes. /// /// Performs the create operation. /// - public void Create(Stream output, IReadOnlyList inputs, FormatCreateOptions options) { + public void Create(Stream output, IReadOnlyList inputs, FormatCreateOptions options) + => CreateCore(output, inputs, options); + + private static void CreateCore(Stream output, IReadOnlyList inputs, FormatCreateOptions options, + RebuildMetadata? saved = null) { ArgumentNullException.ThrowIfNull(output); var w = new Reiser4Writer(); + if (saved is not null) { + w.Label = saved.Label; + w.Uuid = Convert.FromHexString(saved.UuidHex); + w.BlockCount = (ulong)(saved.Length / Reiser4Writer.BlockSize); + w.AddDirectory("", saved.Root); + } // Volume label: prefer the schema knob, falling back to the legacy // password-slot mapping (kept for callers that pre-date the schema). @@ -257,31 +270,33 @@ public void Create(Stream output, IReadOnlyList inputs, Format var sizes = new List(); if (inputs != null) foreach (var i in inputs) { - if (i.IsDirectory) continue; + var name = i.ArchiveName.Replace('\\', '/').TrimEnd('/'); + var metadata = saved?.Entries.GetValueOrDefault(name)?.Metadata; + if (i.IsDirectory) { w.AddDirectory(name, metadata); continue; } var length = i.InMemoryContent?.LongLength ?? new FileInfo(i.FullPath).Length; sizes.Add(length); if (i.InMemoryContent is { } bytes) { - w.AddFile(i.ArchiveName, bytes); + w.AddFile(i.ArchiveName, bytes, metadata); continue; } var path = i.FullPath; - w.AddStreamingFile(i.ArchiveName, length, () => File.OpenRead(path)); + w.AddStreamingFile(i.ArchiveName, length, () => File.OpenRead(path), metadata); } // The requested size is a floor: the volume has to be at least large enough // for the payload's directory chain, data blocks and bitmaps. var sizeBytes = FilesystemSchemaPresets.ParseSize(options?.GetOption("ImageSize", "")); var requested = sizeBytes > 0 ? (ulong)Math.Max(1, sizeBytes / Reiser4Writer.BlockSize) : 0UL; - w.BlockCount = Math.Max(requested, Reiser4Writer.EstimateBlockCount(sizes)); + w.BlockCount = Math.Max(w.BlockCount, Math.Max(requested, Reiser4Writer.EstimateBlockCount(sizes))); w.Write(output); } /// - /// Creates the supported native single-leaf profile from input streams. Each source + /// Creates the supported native node40 profile from input streams. Each source /// is consumed once into a temporary file, then copied in bounded chunks by /// ; no complete file is staged in memory. Directory - /// inputs are currently ignored because this writer emits root-level regular files only. + /// inputs retain empty directories and nested paths. /// public void CreateFromStreams(Stream target, IEnumerable inputs, @@ -293,7 +308,7 @@ public void CreateFromStreams(Stream target, var sizes = new List(); try { foreach (var input in inputs) { - if (input.IsDirectory) continue; + if (input.IsDirectory) { writer.AddDirectory(input.Name); continue; } if (input.Size < 0) throw new ArgumentOutOfRangeException(nameof(inputs), "Input sizes must be non-negative."); var path = Path.Combine(Path.GetTempPath(), "cwb-reiser4-" + Guid.NewGuid().ToString("N")); staged.Add(path); @@ -352,7 +367,7 @@ public void Defragment(Stream archive) private static IReadOnlyList ReadPayloadsForGuard(Stream stream) { stream.Position = 0; using var reader = new Reiser4Reader(stream, leaveOpen: true); - return reader.Entries.Where(e => e.Size > 0).Select(reader.Extract).ToList(); + return reader.Entries.Where(e => !e.IsDirectory && e.Size > 0).Select(reader.Extract).ToList(); } /// Plans the new layout and moves the runs into it, repointing at the end. @@ -398,63 +413,234 @@ public void Defragment(Stream archive, DefragOptions options) { ArgumentNullException.ThrowIfNull(archive); ArgumentNullException.ThrowIfNull(options); - // Moving what is out of place beats writing the volume out again: a file is - // the run of blocks that starts where its directory entry says, so a move - // is the copy plus those eight bytes. What the format will not describe is - // a file whose blocks are no longer one sequence, and the pass refuses - // rather than write that down. - if (archive.CanSeek && archive.Length <= MaxBufferedImageBytes) { - var planned = false; - // The in-place pass is kept only if every payload still reads back: it - // can refuse partway, and a rebuild is the honest answer when it does. - DefragContentGuard.RunOrRebuild(archive, - readContents: ReadPayloadsForGuard, - inPlace: () => { DefragmentWithPlanner(archive, options); planned = true; }, - rebuild: () => planned = false); - if (planned) return; - archive.Position = 0; + var saved = CaptureMetadata(archive); + + using (var reader = new Reiser4Reader(archive)) { + var plannerProfile = reader.NativeNodeBlocks.Count == 2 && reader.NativeNodeBlocks.Contains(23UL) && + reader.NativeNodeBlocks.Contains(24UL) && reader.Entries.All(static entry => !entry.IsDirectory); + if (plannerProfile && archive.CanSeek && archive.Length <= MaxBufferedImageBytes) { + var planned = false; + DefragContentGuard.RunOrRebuild(archive, + readContents: ReadPayloadsForGuard, + inPlace: () => { + DefragmentWithPlanner(archive, options); + var moved = CaptureMetadata(archive); + if (saved.Root is { } root && !MetadataMatches(root, moved.Root) || + saved.Entries.Any(pair => !moved.Entries.TryGetValue(pair.Key, out var entry) || + pair.Value.Metadata is { } metadata && !MetadataMatches(metadata, entry.Metadata))) + throw new InvalidDataException("Reiser4: in-place defrag changed native metadata."); + planned = true; + }, + rebuild: () => planned = false); + if (planned) return; + archive.Position = 0; + } } if (options.Mode is not (DefragMode.ConsolidateAtStart or DefragMode.FillHolesLazy)) throw new NotSupportedException( $"Reiser4 defragmentation supports ConsolidateAtStart and FillHolesLazy; got {options.Mode}."); - Stream? target = null; - var spill = new List<(string Name, string Path, long Size)>(); - DefragRebuilder.RebuildStreaming(archive, options, - readEntries: ReadEntries, - beginWrite: s => target = s, - writeEntry: (name, data) => { - // The volume has to be sized before the first byte is written, so the - // entries are collected first and the writer is built in finishWrite. - var path = Path.GetTempFileName(); - File.WriteAllBytes(path, data); - spill.Add((name, path, data.LongLength)); - }, - finishWrite: () => { - try { - var w = new Reiser4Writer { - BlockCount = Reiser4Writer.EstimateBlockCount(spill.ConvertAll(e => e.Size)), - }; - foreach (var (name, path, size) in spill) { - var captured = path; - w.AddStreamingFile(name, size, () => File.OpenRead(captured)); - } - w.Write(target!); - } finally { - foreach (var (_, path, _) in spill) - try { File.Delete(path); } catch { /* scratch file already gone */ } - } - }); + using var source = new RebuildSource(this, archive); + RebuildVerb.RebuildInPlace(archive, source, new MetadataCreator(saved), + onProgress: options.OnProgress, cancellationToken: options.CancellationToken); + } + + /// Rebuilds tightly while retaining native metadata, or copies through on failure. + public void Shrink(Stream input, Stream output) { + using var staged = RebuildVerb.CreateScratchStream(); + var useStaged = false; + try { + var saved = CaptureMetadata(input) with { Length = 0 }; + using var source = new RebuildSource(this, input); + RebuildVerb.RebuildToStream(input, staged, source, new MetadataCreator(saved)); + useStaged = staged.Length < input.Length; + } catch { /* retain the original when the supported writer cannot represent it */ } + input.Position = 0; + output.Position = 0; + output.SetLength(0); + if (useStaged) { staged.Position = 0; staged.CopyTo(output); } + else input.CopyTo(output); } - private static IEnumerable<(string Name, byte[] Data)> ReadEntries(Stream stream) { - using var reader = new Reiser4Reader(stream); - foreach (var e in reader.Entries) { - using var buffer = new MemoryStream(); - reader.ExtractTo(e, buffer); - yield return (e.Name, buffer.ToArray()); + /// Rebuilds the supported native profile while carrying existing metadata. + public void RebuildStreaming(Stream source, Stream target, LayoutRebuildOptions options) { + ArgumentNullException.ThrowIfNull(options); + if (options.UnitSize is not (0 or Reiser4Writer.BlockSize) || options.MakeSparse || options.DeduplicateWithLinks) + throw new NotSupportedException("Reiser4: this writer uses 4096-byte blocks without sparse or shared-file transforms."); + var parameters = new Dictionary(StringComparer.OrdinalIgnoreCase); + if (options.ImageSize > 0) parameters["ImageSize"] = options.ImageSize.ToString(CultureInfo.InvariantCulture); + if (options.Parameters is not null) + foreach (var pair in options.Parameters) { + if (!pair.Key.Equals("ImageSize", StringComparison.OrdinalIgnoreCase)) + throw new NotSupportedException($"Reiser4: unsupported rebuild parameter '{pair.Key}'."); + parameters[pair.Key] = pair.Value; + } + var saved = CaptureMetadata(source) with { Length = 0 }; + using var sourceOps = new RebuildSource(this, source); + RebuildVerb.RebuildToStream(source, target, sourceOps, new MetadataCreator(saved), parameters); + } + + private static string SafeMemberPath(string root, string name) { + var components = name.Replace('\\', '/').TrimEnd('/').Split('/'); + if (components.Length == 0 || components.Any(static component => component.Length == 0 || + component is "." or ".." || component.Contains(':') || component.Contains('\0'))) + throw new InvalidDataException($"Reiser4: invalid member path '{name}'."); + return Path.Combine(root, Path.Combine(components)); + } + + // One native walk for a rebuild; opening every member must not reparse a wide tree. + private sealed class RebuildSource : IArchiveFormatOperations, IDisposable { + private readonly Reiser4FormatDescriptor _owner; + private readonly Stream _source; + private readonly Reiser4Reader _reader; + private readonly Dictionary _entries; + + internal RebuildSource(Reiser4FormatDescriptor owner, Stream source) { + this._owner = owner; + this._source = source; + this._reader = new Reiser4Reader(source); + if (!this._reader.NativeTreeValid) throw new NotSupportedException("Reiser4: unsupported rebuild source."); + this._entries = this._reader.Entries.ToDictionary(static entry => entry.Name, StringComparer.Ordinal); } + + public List List(Stream stream, string? password) + => ReferenceEquals(stream, this._source) + ? this._reader.Entries.Select(static (entry, index) => new ArchiveEntryInfo(index, entry.Name, + entry.Size, entry.Size, "stored", entry.IsDirectory, false, entry.Metadata?.LastModified)).ToList() + : this._owner.List(stream, password); + + public void Extract(Stream stream, string outputDir, string? password, string[]? files) { + if (!ReferenceEquals(stream, this._source)) { this._owner.Extract(stream, outputDir, password, files); return; } + foreach (var entry in this._reader.Entries) { + if (files is { Length: > 0 } && !MatchesFilter(entry.Name, files)) continue; + var destination = SafeMemberPath(outputDir, entry.Name); + if (entry.IsDirectory) { Directory.CreateDirectory(destination); continue; } + Directory.CreateDirectory(Path.GetDirectoryName(destination)!); + using var output = File.Create(destination); + if (this._reader.ExtractTo(entry, output) != entry.Size) + throw new EndOfStreamException($"Reiser4: truncated source member '{entry.Name}'."); + } + } + + public Stream OpenEntry(Stream archive, string entryName, string? password) { + if (!ReferenceEquals(archive, this._source)) + return ((IArchiveFormatOperations)this._owner).OpenEntry(archive, entryName, password); + if (!this._entries.TryGetValue(entryName, out var entry) || entry.IsDirectory) + throw new FileNotFoundException("Reiser4: source member was not found.", entryName); + if (entry.Size == 0) return new MemoryStream([], writable: false); + var spool = RebuildVerb.CreateScratchStream(); + try { + if (this._reader.ExtractTo(entry, spool) != entry.Size) + throw new EndOfStreamException($"Reiser4: truncated source member '{entryName}'."); + spool.Position = 0; + return spool; + } catch { spool.Dispose(); throw; } + } + + public void Dispose() => this._reader.Dispose(); + } + + private sealed record RebuildMetadata(string Label, string UuidHex, long Length, + Reiser4Reader.FileMetadata? Root, Dictionary Entries); + + private static RebuildMetadata CaptureMetadata(Stream archive) { + using var reader = new Reiser4Reader(archive); + if (!reader.NativeTreeValid) + throw new NotSupportedException("Reiser4: refusing to rebuild an incomplete or unsupported native tree."); + return new(reader.Label, reader.UuidHex, reader.Length, reader.RootMetadata, + reader.Entries.ToDictionary(static entry => entry.Name, StringComparer.Ordinal)); + } + + private sealed class MetadataCreator(RebuildMetadata saved) : IArchiveCreatable { + public void Create(Stream output, IReadOnlyList inputs, FormatCreateOptions options) { + var directoryNames = inputs.Where(static input => input.IsDirectory) + .Select(static input => input.ArchiveName.Replace('\\', '/').TrimEnd('/')).ToArray(); + uint DirectoryLinks(string name) => checked((name.Length == 0 ? 3U : 2U) + (uint)directoryNames.Count(path => { + var slash = path.LastIndexOf('/'); + return (slash < 0 ? "" : path[..slash]) == name; + })); + var entries = saved.Entries.ToDictionary(static pair => pair.Key, pair => pair.Value.IsDirectory && pair.Value.Metadata is { } metadata + ? pair.Value with { Metadata = metadata with { LinkCount = DirectoryLinks(pair.Key) } } : pair.Value, StringComparer.Ordinal); + var effective = saved with { + Entries = entries, Root = saved.Root is { } rootMetadata ? rootMetadata with { LinkCount = DirectoryLinks("") } : null, + }; + CreateCore(output, inputs, options, effective); + using var rebuilt = new Reiser4Reader(output); + if (!rebuilt.NativeTreeValid || rebuilt.Label != saved.Label || rebuilt.UuidHex != saved.UuidHex) + throw new InvalidDataException("Reiser4: rebuilt native tree or volume identity failed verification."); + var expectedNames = inputs.Select(static input => input.ArchiveName.Replace('\\', '/').TrimEnd('/')) + .Order(StringComparer.Ordinal).ToArray(); + if (!rebuilt.Entries.Select(static entry => entry.Name).Order(StringComparer.Ordinal) + .SequenceEqual(expectedNames, StringComparer.Ordinal)) + throw new InvalidDataException("Reiser4: rebuilt namespace failed verification."); + if (effective.Root is { } root && !MetadataMatches(root, rebuilt.RootMetadata)) + throw new InvalidDataException("Reiser4: root metadata failed verification."); + foreach (var entry in rebuilt.Entries) { + if (effective.Entries.TryGetValue(entry.Name, out var original) && original.Metadata is { } before && + !MetadataMatches(before, entry.Metadata)) + throw new InvalidDataException($"Reiser4: metadata was lost for '{entry.Name}'."); + } + } + } + + private static bool MetadataMatches(Reiser4Reader.FileMetadata expected, Reiser4Reader.FileMetadata? actual) + => actual is not null && expected.ObjectId == actual.ObjectId && expected.StatLocality == actual.StatLocality && + expected.Mode == actual.Mode && expected.LinkCount == actual.LinkCount && + expected.UserId == actual.UserId && expected.GroupId == actual.GroupId && + expected.ModifiedTime == actual.ModifiedTime && expected.AccessedTime == actual.AccessedTime && + expected.ChangedTime == actual.ChangedTime && expected.RawStatData.Length == actual.RawStatData.Length && + expected.RawStatData.AsSpan(0, 2).SequenceEqual(actual.RawStatData.AsSpan(0, 2)) && + expected.RawStatData.AsSpan(44).SequenceEqual(actual.RawStatData.AsSpan(44)); + + /// Updates a native object's Unix fields and stat-data extensions through a staged rebuild. + /// The readable, writable, seekable volume to update. + /// The member path, or an empty string for the root directory. + /// Unix fields and stat-data extension bytes to retain. + public void UpdateMetadata(Stream archive, string name, Reiser4Reader.FileMetadata metadata) { + ArgumentNullException.ThrowIfNull(metadata); + var saved = CaptureMetadata(archive); + var normalized = name.Replace('\\', '/').TrimEnd('/'); + var original = normalized.Length == 0 ? saved.Root : saved.Entries.GetValueOrDefault(normalized)?.Metadata; + if (original is null) throw new FileNotFoundException("Reiser4: native object was not found.", name); + if ((metadata.Mode & 0xF000) != (original.Mode & 0xF000)) + throw new NotSupportedException("Reiser4: metadata updates cannot change an object's kind."); + var updated = metadata with { + Size = original.Size, ObjectId = original.ObjectId, StatLocality = original.StatLocality, + RawStatData = metadata.RawStatData.ToArray(), + }; + if (normalized.Length == 0) saved = saved with { Root = updated }; + else saved.Entries[normalized] = saved.Entries[normalized] with { Metadata = updated }; + using var source = new RebuildSource(this, archive); + RebuildVerb.EditViaRebuild(archive, source, new MetadataCreator(saved), static _ => { }); + } + + /// Adds or replaces members while retaining existing stat-data extensions. + public void Add(Stream archive, IReadOnlyList inputs) { + var saved = CaptureMetadata(archive); + using var source = new RebuildSource(this, archive); + RebuildVerb.EditViaRebuild(archive, source, new MetadataCreator(saved), directory => { + foreach (var input in inputs) { + var destination = SafeMemberPath(directory, input.ArchiveName); + if (input.IsDirectory) { Directory.CreateDirectory(destination); continue; } + Directory.CreateDirectory(Path.GetDirectoryName(destination)!); + if (input.InMemoryContent is { } bytes) File.WriteAllBytes(destination, bytes); + else File.Copy(input.FullPath, destination, overwrite: true); + } + }); + } + + /// Removes members and directory subtrees through a metadata-preserving rebuild. + public void Remove(Stream archive, string[] entryNames) { + var saved = CaptureMetadata(archive); + using var source = new RebuildSource(this, archive); + RebuildVerb.EditViaRebuild(archive, source, new MetadataCreator(saved), directory => { + foreach (var name in entryNames) { + var destination = SafeMemberPath(directory, name); + if (Directory.Exists(destination)) Directory.Delete(destination, recursive: true); + else if (File.Exists(destination)) File.Delete(destination); + } + }); } // ── IArchiveWriteConstraints ───────────────────────────────────────── @@ -463,9 +649,7 @@ public void Defragment(Stream archive, DefragOptions options) { /// public bool CanAccept(ArchiveInputInfo input, out string? reason) { ArgumentNullException.ThrowIfNull(input); - reason = input.IsDirectory - ? "this Reiser4 profile writes regular files in the root directory only" - : Reiser4Writer.RejectName(input.ArchiveName); + reason = Reiser4Writer.RejectName(input.IsDirectory ? input.ArchiveName.TrimEnd('/', '\\') : input.ArchiveName); return reason is null; } /// @@ -480,7 +664,7 @@ public bool CanAccept(ArchiveInputInfo input, out string? reason) { /// Gets the accepted inputs description. /// public string AcceptedInputsDescription => - "Reiser4 image; regular files in the root directory, named by the native tree (no sub-directories)."; + "Reiser4 image; regular files and nested directories named by the native tree."; // Bounded read — must NOT pull multi-GB images into memory when the carver // runs us speculatively. Master SB is at 65536, format40 SB at 65536+blocksize @@ -510,7 +694,7 @@ public IEnumerable EnumerateExtents(Stream image) { try { if (image.CanSeek) image.Position = 0; using var reader = new Reiser4Reader(image); - if (!reader.Valid) return []; + if (!reader.NativeTreeValid) return []; var blockSize = reader.BlockSize; // Everything before the first file's blocks is reserved or directory. @@ -532,6 +716,11 @@ public IEnumerable EnumerateExtents(Stream image) { result.Add(new DefragBlockInfo(0, metadataEnd, DefragBlockKind.MetadataReserved, "Reserved blocks and the payload directory")); + foreach (var node in reader.NativeNodeBlocks) + if ((long)node * blockSize >= metadataEnd) + result.Add(new DefragBlockInfo((long)node * blockSize, blockSize, + DefragBlockKind.MetadataReserved, "Native tree node")); + // The bitmaps sit at stride boundaries inside the payload area. for (var block = Reiser4Writer.BlocksPerBitmap; (long)block * blockSize + blockSize <= reader.Length; diff --git a/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Reiser4/Reiser4Reader.cs b/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Reiser4/Reiser4Reader.cs index bfe720466..7cf8b8105 100644 --- a/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Reiser4/Reiser4Reader.cs +++ b/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Reiser4/Reiser4Reader.cs @@ -17,8 +17,6 @@ public sealed class Reiser4Reader : IDisposable { /// Byte offset of the master superblock: block 16 at a 4 KB block size. public const long MasterOffset = 65536; - private const ulong NativeLeafBlock = 24; - private const ulong NativeTwigBlock = 23; private const int ItemHeaderBytes = 38; private const int DirectoryUnitHeaderBytes = 26; private const int TargetKeyBytes = 24; @@ -33,6 +31,7 @@ public sealed class Reiser4Reader : IDisposable { private readonly ImageAccessor _image; private readonly List _entries = []; + private readonly HashSet _nativeNames = new(StringComparer.Ordinal); /// True when the image carries a valid Reiser4 master superblock. public bool Valid { get; } @@ -46,7 +45,7 @@ public sealed class Reiser4Reader : IDisposable { /// Volume UUID from the master superblock, as hex. public string UuidHex { get; } = ""; - /// Files found in the native tree, or in the legacy payload directory. + /// Files and directories found in the native tree, or files in the legacy payload directory. public IReadOnlyList Entries => this._entries; /// Total size of the backing image in bytes. @@ -72,7 +71,10 @@ public Reiser4Reader(Stream stream, bool leaveOpen = true) { // Native metadata is authoritative. The private payload directory is kept // only so old workbench images remain readable while they are migrated. - if (this.TryReadNativeTree()) return; + if (this.TryReadNativeTree()) { this.NativeTreeValid = true; return; } + this._entries.Clear(); + this._nativeNames.Clear(); + this.RootMetadata = null; if (!master.AsSpan(Reiser4Writer.MasterPayloadMarkerOff, Reiser4Writer.PayloadMarker.Length) .SequenceEqual(Reiser4Writer.PayloadMarker)) @@ -83,11 +85,39 @@ public Reiser4Reader(Stream stream, bool leaveOpen = true) { this.ReadLegacyDirectory(dirBlock); } + /// True when the complete supported native namespace was decoded. + public bool NativeTreeValid { get; private set; } + + /// Root stat-data, including inherited plugin settings. + public FileMetadata? RootMetadata { get; private set; } + + internal IReadOnlyList NativeNodeBlocks { get; private set; } = []; + internal readonly record struct NativeRun(ulong Start, ulong Width); - /// One regular file: its name, first data block and byte length. + /// Stat-data fields which can be represented by the current writer. + public sealed record FileMetadata( + long Size, ushort Mode, uint LinkCount, uint UserId, uint GroupId, + uint ModifiedTime, uint AccessedTime, uint ChangedTime, + byte[] RawStatData) { + /// Stable filesystem object identity. + public ulong ObjectId { get; init; } + /// Locality of the object stat key. + public ulong StatLocality { get; init; } + + /// Last-write timestamp, when the seconds value is representable. + public DateTime? LastModified => ModifiedTime == 0 + ? null + : DateTimeOffset.FromUnixTimeSeconds(ModifiedTime).UtcDateTime; + } + + /// One filesystem object and its physical extents. public sealed record Entry(string Name, ulong FirstBlock, long Size) { internal IReadOnlyList? NativeRuns { get; init; } + /// True when this entry is a directory. + public bool IsDirectory { get; init; } + /// On-disk stat-data fields retained for metadata-preserving rebuilds. + public FileMetadata? Metadata { get; init; } } /// Reads a file's contents. Only valid below the array limit. @@ -105,6 +135,7 @@ public byte[] Extract(Entry entry) { public long ExtractTo(Entry entry, Stream destination) { ArgumentNullException.ThrowIfNull(entry); ArgumentNullException.ThrowIfNull(destination); + if (entry.IsDirectory) return 0; if (entry.Size <= 0) return 0; if (entry.NativeRuns is { } runs) @@ -185,129 +216,216 @@ private long ExtractNative(IReadOnlyList runs, long size, Stream dest } /// - /// Reads the single native leaf profile emitted by . - /// Item layout is decoded independently from the writer: item headers name the - /// plugin and key, cde40 units point at object ids, stat40 supplies byte sizes, - /// and extent40 supplies physical runs. Unsupported/corrupt layouts fail closed - /// and leave the legacy fallback available. + /// Walks formatted node40 internal pointers from the format40 root, collects + /// standard leaf1 items, then resolves cde40 names against stat40 keys. Unknown + /// node and item plugins fail closed so the legacy compatibility path remains + /// available for images carrying it. /// private bool TryReadNativeTree() { if (this.BlockSize != Reiser4Writer.BlockSize) return false; + var format = this._image.Read((long)17 * this.BlockSize, this.BlockSize); + if (format.Length != this.BlockSize || !format.AsSpan(52, Reiser4MasterSb.Format40Magic.Length) + .SequenceEqual(Reiser4MasterSb.Format40Magic)) return false; + var root = BinaryPrimitives.ReadUInt64LittleEndian(format.AsSpan(16, 8)); + if (root == 0) return false; + + var leafBlocks = new List<(ulong Block, byte[] Data)>(); + var nodeBlocks = new HashSet(); + if (!this.WalkNativeNodes(root, nodeBlocks, leafBlocks, 0) || leafBlocks.Count == 0) + return false; - var statSizes = new Dictionary(); - var extents = new Dictionary>(); - byte[]? directory = null; - if (!this.TryReadNativeNode(NativeLeafBlock, statSizes, extents, ref directory)) return false; - // Extents are twig items, so a file body is found one level up; a twig that - // is missing or unreadable only means no file has a body. - _ = this.TryReadNativeNode(NativeTwigBlock, statSizes, extents, ref directory); - - if (directory is null) return false; - var parsed = ParseNativeDirectory(directory, statSizes, extents); - if (parsed is null) return false; - this._entries.AddRange(parsed); + var statData = new Dictionary<(ulong Locality, ulong ObjectId), FileMetadata>(); + var extents = new Dictionary<(ulong Locality, ulong ObjectId), List<(ulong Offset, List Runs)>>(); + var directories = new Dictionary>(); + foreach (var (_, leaf) in leafBlocks) { + if (!TryReadLeafItems(leaf, out var leafItems, out var bodiesEnd)) return false; + var byBody = leafItems.OrderBy(static item => item.BodyOffset).ToArray(); + for (var i = 0; i < byBody.Length; ++i) { + var item = byBody[i]; + var end = i + 1 < byBody.Length ? byBody[i + 1].BodyOffset : bodiesEnd; + if (end < item.BodyOffset) return false; + var body = leaf.AsSpan(item.BodyOffset, end - item.BodyOffset); + var objectKey = (item.Locality, item.ObjectId); + switch (item.Plugin) { + case PluginStat40 when item.Minor == MinorStatData && item.Ordering == 0 && item.KeyOffset == 0: + if (body.Length < 44 || (BinaryPrimitives.ReadUInt16LittleEndian(body) & 3) != 3) return false; + var size = BinaryPrimitives.ReadUInt64LittleEndian(body.Slice(8, 8)); + if (size > long.MaxValue) return false; + if (statData.ContainsKey(objectKey)) return false; + statData[objectKey] = new FileMetadata((long)size, + BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(2, 2)), + BinaryPrimitives.ReadUInt32LittleEndian(body.Slice(4, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(body.Slice(16, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(body.Slice(20, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(body.Slice(24, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(body.Slice(28, 4)), + BinaryPrimitives.ReadUInt32LittleEndian(body.Slice(32, 4)), body.ToArray()) { + ObjectId = item.ObjectId, StatLocality = item.Locality, + }; + break; + case PluginExtent40 when item.Minor == MinorFileBody && item.Ordering == 0: + if ((body.Length & 15) != 0) return false; + var runs = new List(body.Length / 16); + for (var p = 0; p < body.Length; p += 16) { + var start = BinaryPrimitives.ReadUInt64LittleEndian(body.Slice(p, 8)); + var width = BinaryPrimitives.ReadUInt64LittleEndian(body.Slice(p + 8, 8)); + if (width != 0) runs.Add(new NativeRun(start, width)); + } + if (!extents.TryGetValue(objectKey, out var pieces)) extents[objectKey] = pieces = []; + pieces.Add((item.KeyOffset, runs)); + break; + case PluginCde40 when item.Minor == 0: + if (!directories.TryGetValue(item.Locality, out var items)) directories[item.Locality] = items = []; + items.Add(body.ToArray()); + break; + case PluginNodePointer when leaf[26] == 2: + if (body.Length != 8) return false; + break; + default: return false; + } + } + } + + foreach (var parts in extents.Values) { + ulong logical = 0; + foreach (var part in parts.OrderBy(static p => p.Offset)) { + if (part.Offset != logical) return false; + foreach (var run in part.Runs) { + if (run.Start < 25 || nodeBlocks.Any(node => node >= run.Start && node - run.Start < run.Width) || run.Start >= (ulong)(this.Length / this.BlockSize) || + run.Width > (ulong)(this.Length / this.BlockSize) - run.Start || + run.Width > (ulong)long.MaxValue / (ulong)this.BlockSize - logical / (ulong)this.BlockSize) return false; + logical += run.Width * (ulong)this.BlockSize; + } + } + } + var joinedExtents = extents.ToDictionary(static pair => pair.Key, + static pair => (IReadOnlyList)pair.Value.OrderBy(static part => part.Offset) + .SelectMany(static part => part.Runs).ToArray()); + var visited = new HashSet(); + if (!WalkNativeDirectory(0x2a, "", statData, joinedExtents, directories, visited)) + return false; + if (!statData.TryGetValue((0x29, 0x2a), out var rootMetadata) || (rootMetadata.Mode & 0xF000) != 0x4000) + return false; + if (statData.Count != this._entries.Count + 1 || directories.Keys.Any(key => !visited.Contains(key)) || + extents.Keys.Any(key => !statData.TryGetValue(key, out var metadata) || (metadata.Mode & 0xF000) != 0x8000)) + return false; + this.RootMetadata = rootMetadata; + this.NativeNodeBlocks = nodeBlocks.ToArray(); return true; } - /// Collects the stat data, extents and directory items of one node40. - private bool TryReadNativeNode(ulong block, Dictionary statSizes, - Dictionary> extents, ref byte[]? directory) { - var offset = checked((long)block * this.BlockSize); - if (offset + this.BlockSize > this._image.Length) return false; + private const ushort PluginNodePointer = 3; - var leaf = this._image.Read(offset, this.BlockSize); - if (leaf.Length != this.BlockSize - || BinaryPrimitives.ReadUInt32LittleEndian(leaf.AsSpan(8, 4)) != unchecked((uint)Reiser4Tree.NodeMagic)) - return false; - - var itemCount = BinaryPrimitives.ReadUInt16LittleEndian(leaf.AsSpan(2, 2)); - var bodiesEnd = BinaryPrimitives.ReadUInt16LittleEndian(leaf.AsSpan(6, 2)); - if (itemCount == 0 || itemCount > (this.BlockSize - Reiser4Tree.NodeHeaderBytes) / ItemHeaderBytes - || bodiesEnd < Reiser4Tree.NodeHeaderBytes || bodiesEnd > this.BlockSize - itemCount * ItemHeaderBytes) + private readonly record struct NativeItem( + ulong Locality, byte Minor, ulong Ordering, ulong ObjectId, ulong KeyOffset, ushort Plugin, ushort BodyOffset); + + private bool WalkNativeNodes(ulong block, HashSet visited, + List<(ulong Block, byte[] Data)> leaves, int depth, int expectedLevel = 0) { + if (depth > 16 || block >= (ulong)(this._image.Length / this.BlockSize) || !visited.Add(block)) return false; + var node = this._image.Read(checked((long)block * this.BlockSize), this.BlockSize); + if (node.Length != this.BlockSize || BinaryPrimitives.ReadUInt16LittleEndian(node) != 0 + || BinaryPrimitives.ReadUInt32LittleEndian(node.AsSpan(8, 4)) != unchecked((uint)Reiser4Tree.NodeMagic)) return false; - - var items = new List(itemCount); - for (var i = 0; i < itemCount; ++i) { - var header = this.BlockSize - (i + 1) * ItemHeaderBytes; - var key0 = BinaryPrimitives.ReadUInt64LittleEndian(leaf.AsSpan(header, 8)); - var bodyOffset = BinaryPrimitives.ReadUInt16LittleEndian(leaf.AsSpan(header + 32, 2)); - var plugin = BinaryPrimitives.ReadUInt16LittleEndian(leaf.AsSpan(header + 36, 2)); - if (bodyOffset < Reiser4Tree.NodeHeaderBytes || bodyOffset > bodiesEnd) return false; - items.Add(new NativeItem( - Minor: (byte)(key0 & 0xF), - Ordering: BinaryPrimitives.ReadUInt64LittleEndian(leaf.AsSpan(header + 8, 8)), - ObjectId: BinaryPrimitives.ReadUInt64LittleEndian(leaf.AsSpan(header + 16, 8)), - KeyOffset: BinaryPrimitives.ReadUInt64LittleEndian(leaf.AsSpan(header + 24, 8)), - Plugin: plugin, - BodyOffset: bodyOffset)); + var level = node[26]; + if (expectedLevel != 0 && level != expectedLevel) return false; + var count = BinaryPrimitives.ReadUInt16LittleEndian(node.AsSpan(2, 2)); + if (count > (this.BlockSize - Reiser4Tree.NodeHeaderBytes) / ItemHeaderBytes) return false; + if (level == 1) { + leaves.Add((block, node)); + return true; } + if (level < 2 || count == 0) return false; + if (!TryReadLeafItems(node, out var items, out var bodiesEnd)) return false; + if (level == 2) leaves.Add((block, node)); + var children = new List(count); var byBody = items.OrderBy(static item => item.BodyOffset).ToArray(); for (var i = 0; i < byBody.Length; ++i) { var item = byBody[i]; var end = i + 1 < byBody.Length ? byBody[i + 1].BodyOffset : bodiesEnd; - if (end < item.BodyOffset) return false; - var body = leaf.AsSpan(item.BodyOffset, end - item.BodyOffset); - - switch (item.Plugin) { - case PluginStat40 when item.Minor == MinorStatData && body.Length >= 16: - var size = BinaryPrimitives.ReadUInt64LittleEndian(body.Slice(8, 8)); - if (size <= long.MaxValue) statSizes[item.ObjectId] = (long)size; - break; - case PluginExtent40 when item.Minor == MinorFileBody: - if ((body.Length & 15) != 0) return false; - var runs = new List(body.Length / 16); - for (var p = 0; p < body.Length; p += 16) { - var start = BinaryPrimitives.ReadUInt64LittleEndian(body.Slice(p, 8)); - var width = BinaryPrimitives.ReadUInt64LittleEndian(body.Slice(p + 8, 8)); - if (width != 0) runs.Add(new NativeRun(start, width)); - } - extents[item.ObjectId] = runs; - break; - case PluginCde40 when !body.IsEmpty: - directory = body.ToArray(); - break; - } + if (item.Plugin == PluginExtent40 && level == 2) continue; + if (item.Plugin != PluginNodePointer || end - item.BodyOffset != sizeof(ulong)) return false; + children.Add(BinaryPrimitives.ReadUInt64LittleEndian(node.AsSpan(item.BodyOffset, sizeof(ulong)))); } - + foreach (var child in children) + if (!this.WalkNativeNodes(child, visited, leaves, depth + 1, level - 1)) return false; return true; } - private readonly record struct NativeItem( - byte Minor, ulong Ordering, ulong ObjectId, ulong KeyOffset, ushort Plugin, ushort BodyOffset); - - private static List? ParseNativeDirectory( - ReadOnlySpan body, - IReadOnlyDictionary statSizes, - IReadOnlyDictionary> extents) { - if (body.Length < 2) return null; - var count = BinaryPrimitives.ReadUInt16LittleEndian(body); - var unitsStart = 2 + count * DirectoryUnitHeaderBytes; - if (unitsStart > body.Length) return null; - - var result = new List(); + private bool TryReadLeafItems(byte[] leaf, out List items, out int bodiesEnd) { + items = []; + var count = BinaryPrimitives.ReadUInt16LittleEndian(leaf.AsSpan(2, 2)); + bodiesEnd = BinaryPrimitives.ReadUInt16LittleEndian(leaf.AsSpan(6, 2)); + if (count == 0 || count > (this.BlockSize - Reiser4Tree.NodeHeaderBytes) / ItemHeaderBytes + || bodiesEnd < Reiser4Tree.NodeHeaderBytes || bodiesEnd > this.BlockSize - count * ItemHeaderBytes) + return false; for (var i = 0; i < count; ++i) { - var header = 2 + i * DirectoryUnitHeaderBytes; - var ordering = BinaryPrimitives.ReadUInt64LittleEndian(body.Slice(header, 8)); - var objectIdPart = BinaryPrimitives.ReadUInt64LittleEndian(body.Slice(header + 8, 8)); - var offsetPart = BinaryPrimitives.ReadUInt64LittleEndian(body.Slice(header + 16, 8)); - var unit = BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(header + 24, 2)); - var unitEnd = i + 1 < count - ? BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(header + DirectoryUnitHeaderBytes + 24, 2)) - : body.Length; - if (unit < unitsStart || unitEnd < unit || unitEnd > body.Length || unit + TargetKeyBytes > unitEnd) - return null; - - var targetObjectId = BinaryPrimitives.ReadUInt64LittleEndian(body.Slice(unit + 16, 8)); - var name = DecodeName(body.Slice(unit, unitEnd - unit), ordering, objectIdPart, offsetPart); - if (name.Length == 0 || name is "." or "..") continue; - if (!statSizes.TryGetValue(targetObjectId, out var size)) continue; - extents.TryGetValue(targetObjectId, out var runs); - runs ??= Array.Empty(); - if (size > 0 && runs.Count == 0) return null; - result.Add(new Entry(name, runs.Count == 0 ? 0 : runs[0].Start, size) { NativeRuns = runs }); + var header = this.BlockSize - (i + 1) * ItemHeaderBytes; + var key0 = BinaryPrimitives.ReadUInt64LittleEndian(leaf.AsSpan(header, 8)); + var bodyOffset = BinaryPrimitives.ReadUInt16LittleEndian(leaf.AsSpan(header + 32, 2)); + var plugin = BinaryPrimitives.ReadUInt16LittleEndian(leaf.AsSpan(header + 36, 2)); + if (bodyOffset < Reiser4Tree.NodeHeaderBytes || bodyOffset > bodiesEnd) return false; + items.Add(new NativeItem(key0 >> 4, (byte)(key0 & 0xf), + BinaryPrimitives.ReadUInt64LittleEndian(leaf.AsSpan(header + 8, 8)), + BinaryPrimitives.ReadUInt64LittleEndian(leaf.AsSpan(header + 16, 8)), + BinaryPrimitives.ReadUInt64LittleEndian(leaf.AsSpan(header + 24, 8)), plugin, bodyOffset)); } - return result; + return true; + } + + private bool WalkNativeDirectory(ulong directoryId, string path, + IReadOnlyDictionary<(ulong Locality, ulong ObjectId), FileMetadata> statData, + IReadOnlyDictionary<(ulong Locality, ulong ObjectId), IReadOnlyList> extents, + IReadOnlyDictionary> directories, HashSet visited) { + if (path.Count(static c => c == '/') > 128 || !visited.Add(directoryId)) return false; + if (!directories.TryGetValue(directoryId, out var directoryItems)) return false; + + foreach (var directory in directoryItems) { + if (directory.Length < 2) return false; + var count = BinaryPrimitives.ReadUInt16LittleEndian(directory); + var unitsStart = 2 + count * DirectoryUnitHeaderBytes; + if (unitsStart > directory.Length) return false; + + for (var i = 0; i < count; ++i) { + var header = 2 + i * DirectoryUnitHeaderBytes; + var ordering = BinaryPrimitives.ReadUInt64LittleEndian(directory.AsSpan(header, 8)); + var objectIdPart = BinaryPrimitives.ReadUInt64LittleEndian(directory.AsSpan(header + 8, 8)); + var offsetPart = BinaryPrimitives.ReadUInt64LittleEndian(directory.AsSpan(header + 16, 8)); + var unit = BinaryPrimitives.ReadUInt16LittleEndian(directory.AsSpan(header + 24, 2)); + var unitEnd = i + 1 < count + ? BinaryPrimitives.ReadUInt16LittleEndian(directory.AsSpan(header + DirectoryUnitHeaderBytes + 24, 2)) + : directory.Length; + if (unit < unitsStart || unitEnd < unit || unitEnd > directory.Length || unit + TargetKeyBytes > unitEnd) + return false; + + var name = DecodeName(directory.AsSpan(unit, unitEnd - unit), ordering, objectIdPart, offsetPart); + if (name.Length == 0 || name is "." or "..") continue; + if (name.Contains('/') || name.Contains('\\') || name.Contains('\0') || name.Contains(':') || name.Any(static c => c > 127)) return false; + var targetLocality = BinaryPrimitives.ReadUInt64LittleEndian(directory.AsSpan(unit, 8)) >> 4; + var targetObjectId = BinaryPrimitives.ReadUInt64LittleEndian(directory.AsSpan(unit + 16, 8)); + if (!statData.TryGetValue((targetLocality, targetObjectId), out var metadata)) return false; + + var fullName = string.IsNullOrEmpty(path) ? name : $"{path}/{name}"; + if (!this._nativeNames.Add(fullName)) return false; + var modeType = metadata.Mode & 0xF000; + if (modeType == 0x4000) { + this._entries.Add(new Entry(fullName, 0, 0) { IsDirectory = true, Metadata = metadata }); + if (!this.WalkNativeDirectory(targetObjectId, fullName, statData, extents, directories, visited)) + return false; + continue; + } + + if (modeType != 0x8000) return false; + extents.TryGetValue((targetLocality, targetObjectId), out var runs); + runs ??= Array.Empty(); + if (runs.Aggregate(0UL, static (sum, run) => sum + run.Width) * (ulong)this.BlockSize < (ulong)metadata.Size) return false; + this._entries.Add(new Entry(fullName, runs.Count == 0 ? 0 : runs[0].Start, metadata.Size) { + NativeRuns = runs, + Metadata = metadata, + }); + } + } + return true; } private static string DecodeName( @@ -317,7 +435,7 @@ private static string DecodeName( var stored = unit[TargetKeyBytes..]; var nul = stored.IndexOf((byte)0); if (nul >= 0) stored = stored[..nul]; - return Encoding.ASCII.GetString(stored); + return Encoding.Latin1.GetString(stored); } Span name = stackalloc byte[23]; @@ -325,7 +443,7 @@ private static string DecodeName( length += Unpack(name[length..], ordering & 0x00FF_FFFF_FFFF_FFFFUL, 7); length += Unpack(name[length..], objectId, 8); length += Unpack(name[length..], offset, 8); - return Encoding.ASCII.GetString(name[..length]); + return Encoding.Latin1.GetString(name[..length]); } private static int Unpack(Span destination, ulong value, int bytes) { diff --git a/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Reiser4/Reiser4Tree.cs b/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Reiser4/Reiser4Tree.cs index 1d1ab8940..533dd6ea6 100644 --- a/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Reiser4/Reiser4Tree.cs +++ b/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Reiser4/Reiser4Tree.cs @@ -5,9 +5,8 @@ namespace FileSystem.Reiser4; /// -/// Builds the two-level reiser4 tree: a leaf with the root directory's stat data -/// and entries plus every file's stat data, and above it the twig that points at -/// that leaf and holds each file's extents. +/// Builds a reiser4 node40 tree containing directory stat data and entries, +/// plus stat data and extents for regular files. /// /// /// A node is a 28-byte header, item bodies growing up from there, and an @@ -15,12 +14,6 @@ namespace FileSystem.Reiser4; /// four-word key, the body's offset, flags, and which plugin reads it. Items sit /// in key order. /// -/// Extents are twig items, not leaf items: reiser4 keeps the pointers to a -/// file body's unformatted blocks one level above the leaves, next to the pointers -/// to those leaves. fsck.reiser4 rejects a level-1 node holding an extent -/// as broken ("node level does not match the item type"), so the body keys, which -/// sort after every stat data and entry, follow the leaf pointer in the twig. -/// /// A key is four little-endian words: the locality in the top sixty bits of /// the first with the item's type in its low four, an ordering, an object id, and /// an offset. A file's stat data and its body share the first three and differ in @@ -41,7 +34,6 @@ internal static class Reiser4Tree { private const int PluginStat40 = 0x0; private const int PluginCde40 = 0x2; private const int PluginExtent40 = 0x5; - private const int PluginNodePtr40 = 0x3; private const byte MinorFileName = 0; private const byte MinorStatData = 1; @@ -63,11 +55,28 @@ internal static class Reiser4Tree { internal readonly record struct Run(ulong Start, ulong Width); /// A file to put in the tree. - internal sealed class Entry { + internal sealed record Entry { internal required string Name { get; init; } + internal required ulong ParentObjectId { get; init; } internal required ulong ObjectId { get; init; } internal required long Size { get; init; } internal required IReadOnlyList Runs { get; init; } + internal Reiser4Reader.FileMetadata? Metadata { get; init; } + } + + private sealed record Item(ulong Locality, byte Minor, ulong Ordering, ulong ObjectId, ulong Offset, + int Plugin, byte[] Body); + + internal sealed record Layout(ulong NextObjectId, ulong ObjectCount, byte Height, IReadOnlyDictionary Nodes); + + private sealed class DirectoryNode { + internal required string Path { get; init; } + internal required string Name { get; init; } + internal required ulong ObjectId { get; init; } + internal required ulong ParentObjectId { get; init; } + internal required ulong StatLocality { get; init; } + internal required ulong ParentStatLocality { get; init; } + internal List<(string Name, ulong ObjectId)> Children { get; } = []; } /// The name packed into a word, big-endian, from . @@ -134,29 +143,42 @@ private static (ulong Ordering, ulong ObjectId, ulong Offset, bool Hashed) NameK } /// A stat data saying what a file is and how long. - private static byte[] StatData(ushort mode, uint links, ulong size, ulong bytes, uint time) { + private static byte[] StatData(ushort mode, uint links, ulong size, ulong bytes, uint time, + Reiser4Reader.FileMetadata? metadata = null) { + if (metadata is { } saved && saved.RawStatData.Length >= 44) { + var preserved = saved.RawStatData.ToArray(); + BinaryPrimitives.WriteUInt16LittleEndian(preserved.AsSpan(2), saved.Mode); + BinaryPrimitives.WriteUInt32LittleEndian(preserved.AsSpan(4), saved.LinkCount); + BinaryPrimitives.WriteUInt64LittleEndian(preserved.AsSpan(8), size); + BinaryPrimitives.WriteUInt32LittleEndian(preserved.AsSpan(16), saved.UserId); + BinaryPrimitives.WriteUInt32LittleEndian(preserved.AsSpan(20), saved.GroupId); + BinaryPrimitives.WriteUInt32LittleEndian(preserved.AsSpan(24), saved.ModifiedTime); + BinaryPrimitives.WriteUInt32LittleEndian(preserved.AsSpan(28), saved.AccessedTime); + BinaryPrimitives.WriteUInt32LittleEndian(preserved.AsSpan(32), saved.ChangedTime); + BinaryPrimitives.WriteUInt64LittleEndian(preserved.AsSpan(36), bytes); + return preserved; + } var body = new byte[2 + 14 + 28]; BinaryPrimitives.WriteUInt16LittleEndian(body, 0x3); // light-weight and unix - BinaryPrimitives.WriteUInt16LittleEndian(body.AsSpan(2), mode); - BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(4), links); + BinaryPrimitives.WriteUInt16LittleEndian(body.AsSpan(2), metadata?.Mode ?? mode); + BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(4), metadata?.LinkCount ?? links); BinaryPrimitives.WriteUInt64LittleEndian(body.AsSpan(8), size); - BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(16), 0); // uid - BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(20), 0); // gid - BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(24), time); - BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(28), time); - BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(32), time); + BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(16), metadata?.UserId ?? 0); + BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(20), metadata?.GroupId ?? 0); + BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(24), metadata?.ModifiedTime ?? time); + BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(28), metadata?.AccessedTime ?? time); + BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(32), metadata?.ChangedTime ?? time); BinaryPrimitives.WriteUInt64LittleEndian(body.AsSpan(36), bytes); return body; } /// The root's entries, its own two and one for each file. - private static byte[] Directory(ulong rootLocality, ulong rootObjectId, - IReadOnlyList files, int blockSize) { + private static IEnumerable Directory(DirectoryNode directory, int blockSize) { var names = new List<(string Name, ulong Locality, ulong ObjectId)> { - (".", rootLocality, rootObjectId), - ("..", rootLocality, rootObjectId), + (".", directory.StatLocality, directory.ObjectId), + ("..", directory.ParentStatLocality, directory.ParentObjectId), }; - foreach (var file in files) names.Add((file.Name, rootObjectId, file.ObjectId)); + foreach (var child in directory.Children) names.Add((child.Name, directory.ObjectId, child.ObjectId)); var keyed = new List<(ulong Ordering, ulong ObjectId, ulong Offset, bool Hashed, string Name, ulong Locality, ulong Target)>(); @@ -172,145 +194,251 @@ private static byte[] Directory(ulong rootLocality, ulong rootObjectId, const int unitHeaderBytes = 26; const int targetKeyBytes = 24; - // A hashed name is kept beside the key it did not fit into, NUL-terminated. - var unitBytes = 0; - foreach (var entry in keyed) - unitBytes += targetKeyBytes + (entry.Hashed ? Encoding.ASCII.GetByteCount(entry.Name) + 1 : 0); - - var body = new byte[2 + keyed.Count * unitHeaderBytes + unitBytes]; - BinaryPrimitives.WriteUInt16LittleEndian(body, (ushort)keyed.Count); - - var unit = 2 + keyed.Count * unitHeaderBytes; - for (var i = 0; i < keyed.Count; ++i) { - var (ordering, objectId, offset, hashed, name, locality, target) = keyed[i]; - var header = 2 + i * unitHeaderBytes; - BinaryPrimitives.WriteUInt64LittleEndian(body.AsSpan(header), ordering); - BinaryPrimitives.WriteUInt64LittleEndian(body.AsSpan(header + 8), objectId); - BinaryPrimitives.WriteUInt64LittleEndian(body.AsSpan(header + 16), offset); - BinaryPrimitives.WriteUInt16LittleEndian(body.AsSpan(header + 24), (ushort)unit); - - // What the entry points at: the first three words of its target's stat-data key. - BinaryPrimitives.WriteUInt64LittleEndian(body.AsSpan(unit), locality << 4 | MinorStatData); - BinaryPrimitives.WriteUInt64LittleEndian(body.AsSpan(unit + 8), 0); - BinaryPrimitives.WriteUInt64LittleEndian(body.AsSpan(unit + 16), target); - unit += targetKeyBytes; - - if (!hashed) continue; - - var nameBytes = Encoding.ASCII.GetBytes(name); - nameBytes.CopyTo(body.AsSpan(unit)); - unit += nameBytes.Length + 1; + var first = 0; + while (first < keyed.Count) { + var last = first; + var bodySize = 2; + while (last < keyed.Count) { + var entrySize = unitHeaderBytes + targetKeyBytes + + (keyed[last].Hashed ? Encoding.ASCII.GetByteCount(keyed[last].Name) + 1 : 0); + if (bodySize + entrySize > blockSize - NodeHeaderBytes - ItemHeaderBytes) break; + bodySize += entrySize; + ++last; + } + if (last == first) throw new InvalidDataException("Reiser4: a directory unit exceeds node capacity."); + var body = new byte[bodySize]; + BinaryPrimitives.WriteUInt16LittleEndian(body, checked((ushort)(last - first))); + var unit = 2 + (last - first) * unitHeaderBytes; + for (var i = first; i < last; ++i) { + var entry = keyed[i]; + var header = 2 + (i - first) * unitHeaderBytes; + BinaryPrimitives.WriteUInt64LittleEndian(body.AsSpan(header), entry.Ordering); + BinaryPrimitives.WriteUInt64LittleEndian(body.AsSpan(header + 8), entry.ObjectId); + BinaryPrimitives.WriteUInt64LittleEndian(body.AsSpan(header + 16), entry.Offset); + BinaryPrimitives.WriteUInt16LittleEndian(body.AsSpan(header + 24), checked((ushort)unit)); + BinaryPrimitives.WriteUInt64LittleEndian(body.AsSpan(unit), entry.Locality << 4 | MinorStatData); + BinaryPrimitives.WriteUInt64LittleEndian(body.AsSpan(unit + 16), entry.Target); + unit += targetKeyBytes; + if (entry.Hashed) { + var bytes = Encoding.ASCII.GetBytes(entry.Name); + bytes.CopyTo(body.AsSpan(unit)); + unit += bytes.Length + 1; + } + } + var firstKey = keyed[first]; + yield return new(directory.ObjectId, MinorFileName, firstKey.Ordering, firstKey.ObjectId, + firstKey.Offset, PluginCde40, body); + first = last; } - - return body; } /// - /// Rewrites so it holds the root and every file given. + /// Builds leaves, twig extents and pointer levels from the captured root defaults. /// - /// The leaf as mkfs left it, whose root stat data is kept. - internal static void Build(Span leaf, int blockSize, uint mkfsId, uint time, - ulong rootLocality, ulong rootObjectId, IReadOnlyList files) { + internal static Layout Build(byte[] leaf, int blockSize, uint mkfsId, uint time, + ulong rootLocality, ulong rootObjectId, IReadOnlyList sourceFiles, + IReadOnlyDictionary? savedDirectories, Func allocate) { // The root's own stat data, kept as mkfs wrote it — it carries the plugin set // every file below it inherits, which this does not attempt to rebuild. var rootStatOffset = BinaryPrimitives.ReadUInt16LittleEndian(leaf[(blockSize - ItemHeaderBytes + 32)..]); var rootStatLength = BinaryPrimitives.ReadUInt16LittleEndian(leaf[(blockSize - 2 * ItemHeaderBytes + 32)..]) - rootStatOffset; - var rootStat = leaf.Slice(rootStatOffset, rootStatLength).ToArray(); - var directory = Directory(rootLocality, rootObjectId, files, blockSize); - - // A directory's size is its entry count and its bytes the space its entries - // take (every unit, without the item's two-byte count); fsck.reiser4 checks - // both against the entries it finds. mkfs wrote them for "." and ".." alone. - if ((BinaryPrimitives.ReadUInt16LittleEndian(rootStat) & 0x3) != 0x3 || rootStat.Length < 44) - throw new InvalidOperationException("Reiser4: the root stat-data template lacks the light-weight and unix extensions."); - BinaryPrimitives.WriteUInt64LittleEndian(rootStat.AsSpan(8), (ulong)(2 + files.Count)); - BinaryPrimitives.WriteUInt64LittleEndian(rootStat.AsSpan(36), (ulong)(directory.Length - 2)); - - var bodies = new List<(ulong Locality, byte Minor, ulong Ordering, ulong ObjectId, ulong Offset, - int Plugin, byte[] Body)> { - (rootLocality, MinorStatData, 0, rootObjectId, 0, PluginStat40, rootStat), - (rootObjectId, MinorFileName, 0, 0, 0, PluginCde40, directory), + var rootStat = leaf.AsSpan(rootStatOffset, rootStatLength).ToArray(); + if (savedDirectories?.GetValueOrDefault("") is { } rootMetadata) + rootStat = StatData(rootMetadata.Mode, rootMetadata.LinkCount, (ulong)rootMetadata.Size, + BinaryPrimitives.ReadUInt64LittleEndian(rootMetadata.RawStatData.AsSpan(36)), time, rootMetadata); + + var rootDirectory = new DirectoryNode { + Path = "", Name = "", ObjectId = rootObjectId, ParentObjectId = rootObjectId, + StatLocality = rootLocality, ParentStatLocality = rootLocality, }; - - foreach (var file in files) { - var held = 0UL; - foreach (var run in file.Runs) held += run.Width; - bodies.Add((rootObjectId, MinorStatData, 0, file.ObjectId, 0, PluginStat40, - StatData(0x81A4, 1, (ulong)file.Size, held * (ulong)blockSize, time))); + var directories = new List { rootDirectory }; + var byPath = new Dictionary(StringComparer.Ordinal) { [""] = rootDirectory }; + var knownIds = sourceFiles.Select(static entry => entry.Metadata?.ObjectId ?? 0) + .Concat(savedDirectories?.Values.Select(static metadata => metadata?.ObjectId ?? 0) ?? []); + var nextObjectId = checked(Math.Max(0xFFFFUL, Math.Max(rootObjectId, knownIds.DefaultIfEmpty().Max())) + 1); + var assignedIds = new HashSet { rootObjectId }; + ulong ObjectId(Reiser4Reader.FileMetadata? metadata) { + var id = metadata?.ObjectId is > 0 ? metadata.ObjectId : nextObjectId; + if (metadata?.ObjectId is not > 0) nextObjectId = checked(nextObjectId + 1); + if (id <= rootObjectId || !assignedIds.Add(id)) + throw new InvalidDataException("Reiser4: duplicate or reserved object identity."); + return id; } + var fileParents = new List<(Entry Source, ulong ParentObjectId, ulong ObjectId)>(); + + var allSources = (savedDirectories?.Keys.Where(static path => path.Length > 0) ?? []) + .Select(static path => new Entry { Name = path + "/", ParentObjectId = 0, ObjectId = 0, Size = 0, Runs = [] }) + .Concat(sourceFiles); + foreach (var source in allSources) { + var isDirectory = source.Name.EndsWith('/'); + var components = source.Name.Replace('\\', '/').Split('/', StringSplitOptions.RemoveEmptyEntries); + if (components.Length == 0 || components.Any(static component => component is "." or ".." || + component.Contains(':') || component.Contains('\0') || component.Any(static c => c > 127))) + throw new InvalidDataException($"Reiser4: invalid archive path '{source.Name}'."); + + var parentPath = ""; + var parent = rootDirectory; + for (var i = 0; i < components.Length - (isDirectory ? 0 : 1); ++i) { + var path = parentPath.Length == 0 ? components[i] : $"{parentPath}/{components[i]}"; + if (!byPath.TryGetValue(path, out var directory)) { + if (parent.Children.Any(child => child.Name == components[i])) + throw new InvalidDataException($"Reiser4: file/directory collision at '{path}'."); + directory = new DirectoryNode { + Path = path, Name = components[i], ObjectId = ObjectId(savedDirectories?.GetValueOrDefault(path)), ParentObjectId = parent.ObjectId, + StatLocality = parent.ObjectId, ParentStatLocality = parent.StatLocality, + }; + byPath.Add(path, directory); + directories.Add(directory); + parent.Children.Add((directory.Name, directory.ObjectId)); + } + parent = directory; + parentPath = path; + } - WriteNode(leaf, blockSize, mkfsId, level: 1, bodies); - } + if (isDirectory) continue; + var leafName = components[^1]; + if (parent.Children.Any(child => string.Equals(child.Name, leafName, StringComparison.Ordinal))) + throw new InvalidDataException($"Reiser4: duplicate path or file/directory collision at '{source.Name}'."); + var objectId = ObjectId(source.Metadata); + parent.Children.Add((leafName, objectId)); + fileParents.Add((source with { Name = leafName }, parent.ObjectId, objectId)); + } - /// - /// Rewrites so it points at the leaf and carries every - /// file's extents, in key order after that pointer. - /// - /// The twig as mkfs left it, whose single leaf pointer is kept. - internal static void BuildTwig(Span twig, int blockSize, uint mkfsId, ulong rootObjectId, IReadOnlyList files) { - var pointerHeader = blockSize - ItemHeaderBytes; - if (BinaryPrimitives.ReadUInt16LittleEndian(twig[2..]) != 1 - || BinaryPrimitives.ReadUInt16LittleEndian(twig[(pointerHeader + 36)..]) != PluginNodePtr40) - throw new InvalidOperationException("Reiser4: the twig template does not hold exactly one leaf pointer."); - var pointerOffset = BinaryPrimitives.ReadUInt16LittleEndian(twig[(pointerHeader + 32)..]); - var key = twig.Slice(pointerHeader, 32); - var first = BinaryPrimitives.ReadUInt64LittleEndian(key); - - var bodies = new List<(ulong Locality, byte Minor, ulong Ordering, ulong ObjectId, ulong Offset, - int Plugin, byte[] Body)> { - (first >> 4, (byte)(first & 0xF), BinaryPrimitives.ReadUInt64LittleEndian(key[8..]), - BinaryPrimitives.ReadUInt64LittleEndian(key[16..]), BinaryPrimitives.ReadUInt64LittleEndian(key[24..]), - PluginNodePtr40, twig.Slice(pointerOffset, 8).ToArray()), + var files = fileParents.Select(static file => new Entry { + Name = file.Source.Name, ParentObjectId = file.ParentObjectId, ObjectId = file.ObjectId, + Size = file.Source.Size, Runs = file.Source.Runs, Metadata = file.Source.Metadata, + }).ToArray(); + + var rootItems = Directory(rootDirectory, blockSize).ToArray(); + BinaryPrimitives.WriteUInt32LittleEndian(rootStat.AsSpan(4), checked(3U + (uint)directories.Count(directory => directory != rootDirectory && directory.ParentObjectId == rootObjectId))); + BinaryPrimitives.WriteUInt64LittleEndian(rootStat.AsSpan(8), (ulong)rootDirectory.Children.Count + 2); + BinaryPrimitives.WriteUInt64LittleEndian(rootStat.AsSpan(36), (ulong)rootItems.Sum(static item => item.Body.Length - 2)); + var bodies = new List { + new(rootLocality, MinorStatData, 0, rootObjectId, 0, PluginStat40, rootStat), }; + bodies.AddRange(rootItems); + + foreach (var directory in directories.Skip(1)) { + var directoryItems = Directory(directory, blockSize).ToArray(); + bodies.Add(new(directory.StatLocality, MinorStatData, 0, directory.ObjectId, 0, PluginStat40, + StatData(0x41ED, checked(2U + (uint)directories.Count(child => child.ParentObjectId == directory.ObjectId)), (ulong)directory.Children.Count + 2, (ulong)directoryItems.Sum(static item => item.Body.Length - 2), time, + savedDirectories?.GetValueOrDefault(directory.Path)))); + bodies.AddRange(directoryItems); + } - // Body keys share the root's object id as locality and differ only in the - // file's object id, so object-id order is key order. - foreach (var file in files.OrderBy(static f => f.ObjectId)) { + foreach (var file in files) { + var held = file.Runs.Aggregate(0UL, static (sum, run) => checked(sum + run.Width)); + bodies.Add(new(file.ParentObjectId, MinorStatData, 0, file.ObjectId, 0, PluginStat40, + StatData(0x81A4, 1, (ulong)file.Size, held * (ulong)blockSize, time, file.Metadata))); if (file.Runs.Count == 0) continue; - - var extents = new byte[file.Runs.Count * 16]; - for (var i = 0; i < file.Runs.Count; ++i) { - BinaryPrimitives.WriteUInt64LittleEndian(extents.AsSpan(i * 16), file.Runs[i].Start); - BinaryPrimitives.WriteUInt64LittleEndian(extents.AsSpan(i * 16 + 8), file.Runs[i].Width); + var runsPerItem = (blockSize - NodeHeaderBytes - ItemHeaderBytes) / 16; + ulong logicalOffset = 0; + for (var firstRun = 0; firstRun < file.Runs.Count; firstRun += runsPerItem) { + var count = Math.Min(runsPerItem, file.Runs.Count - firstRun); + var extents = new byte[count * 16]; + for (var i = 0; i < count; ++i) { + var run = file.Runs[firstRun + i]; + BinaryPrimitives.WriteUInt64LittleEndian(extents.AsSpan(i * 16), run.Start); + BinaryPrimitives.WriteUInt64LittleEndian(extents.AsSpan(i * 16 + 8), run.Width); + } + bodies.Add(new(file.ParentObjectId, MinorFileBody, 0, file.ObjectId, logicalOffset, PluginExtent40, extents)); + for (var i = 0; i < count; ++i) + logicalOffset = checked(logicalOffset + file.Runs[firstRun + i].Width * (ulong)blockSize); } + } - bodies.Add((rootObjectId, MinorFileBody, 0, file.ObjectId, 0, PluginExtent40, extents)); + bodies.Sort(static (a, b) => { + var c = (a.Locality << 4 | a.Minor).CompareTo(b.Locality << 4 | b.Minor); + if (c != 0) return c; + c = a.Ordering.CompareTo(b.Ordering); + if (c != 0) return c; + c = a.ObjectId.CompareTo(b.ObjectId); + return c != 0 ? c : a.Offset.CompareTo(b.Offset); + }); + + var nodes = new Dictionary(); + var twigItems = new List(); + var pending = new List(); + var firstLeaf = true; + void FlushLeaves() { + foreach (var group in Pack(pending, blockSize)) { + var block = firstLeaf ? 24UL : allocate(); + firstLeaf = false; + nodes.Add(block, EncodeNode(group, blockSize, mkfsId, 1)); + twigItems.Add(group[0] with { Plugin = 3, Body = PointerBody(block) }); + } + pending.Clear(); + } + // A leaf cannot straddle a key range occupied by a twig extent item. + // End the leaf run before that extent, then resume after it. + foreach (var item in bodies) { + if (item.Plugin == PluginExtent40) { + FlushLeaves(); + twigItems.Add(item); + } else pending.Add(item); + } + FlushLeaves(); + byte level = 2; + var parentItems = twigItems; + while (true) { + var groups = Pack(parentItems, blockSize).ToList(); + if (groups.Count == 1) { + nodes.Add(23, EncodeNode(groups[0], blockSize, mkfsId, level)); + return new(nextObjectId, (ulong)assignedIds.Count, level, nodes); + } + parentItems = []; + foreach (var group in groups) { + var block = allocate(); + nodes.Add(block, EncodeNode(group, blockSize, mkfsId, level)); + parentItems.Add(group[0] with { Plugin = 3, Body = PointerBody(block) }); + } + level = checked((byte)(level + 1)); } + } - WriteNode(twig, blockSize, mkfsId, level: 2, bodies); + private static byte[] PointerBody(ulong block) { + var body = new byte[8]; + BinaryPrimitives.WriteUInt64LittleEndian(body, block); + return body; } - /// Lays out as one node40 at . - private static void WriteNode(Span leaf, int blockSize, uint mkfsId, byte level, - List<(ulong Locality, byte Minor, ulong Ordering, ulong ObjectId, ulong Offset, int Plugin, byte[] Body)> bodies) { - leaf[..blockSize].Clear(); - var at = NodeHeaderBytes; - for (var i = 0; i < bodies.Count; ++i) { - var (locality, minor, ordering, objectId, offset, plugin, body) = bodies[i]; - if (at + body.Length > blockSize - (i + 1) * ItemHeaderBytes) - throw new InvalidOperationException( - $"Reiser4: {bodies.Count} items do not fit one {blockSize}-byte node; this writer builds a single leaf under a single twig."); + private static IEnumerable> Pack(IReadOnlyList items, int blockSize) { + var group = new List(); + var bytes = NodeHeaderBytes; + foreach (var item in items) { + var need = checked(item.Body.Length + ItemHeaderBytes); + if (need > blockSize - NodeHeaderBytes) + throw new InvalidDataException("Reiser4: an indivisible item exceeds node capacity."); + if (bytes + need > blockSize) { + yield return group; + group = []; + bytes = NodeHeaderBytes; + } + group.Add(item); + bytes += need; + } + if (group.Count > 0) yield return group; + } - body.CopyTo(leaf[at..]); + private static byte[] EncodeNode(IReadOnlyList items, int blockSize, uint mkfsId, byte level) { + var node = new byte[blockSize]; + var at = NodeHeaderBytes; + for (var i = 0; i < items.Count; ++i) { + var item = items[i]; + item.Body.CopyTo(node.AsSpan(at)); var header = blockSize - (i + 1) * ItemHeaderBytes; - WriteKey(leaf[header..], locality, minor, ordering, objectId, offset); - BinaryPrimitives.WriteUInt16LittleEndian(leaf[(header + 32)..], (ushort)at); - BinaryPrimitives.WriteUInt16LittleEndian(leaf[(header + 34)..], 0); - BinaryPrimitives.WriteUInt16LittleEndian(leaf[(header + 36)..], (ushort)plugin); - at += body.Length; + WriteKey(node.AsSpan(header), item.Locality, item.Minor, item.Ordering, item.ObjectId, item.Offset); + BinaryPrimitives.WriteUInt16LittleEndian(node.AsSpan(header + 32), checked((ushort)at)); + BinaryPrimitives.WriteUInt16LittleEndian(node.AsSpan(header + 36), checked((ushort)item.Plugin)); + at += item.Body.Length; } - - BinaryPrimitives.WriteUInt16LittleEndian(leaf, 0); // node40 - BinaryPrimitives.WriteUInt16LittleEndian(leaf[2..], (ushort)bodies.Count); - BinaryPrimitives.WriteUInt16LittleEndian(leaf[4..], - (ushort)(blockSize - bodies.Count * ItemHeaderBytes - at)); - BinaryPrimitives.WriteUInt16LittleEndian(leaf[6..], (ushort)at); - BinaryPrimitives.WriteUInt32LittleEndian(leaf[8..], unchecked((uint)NodeMagic)); - BinaryPrimitives.WriteUInt32LittleEndian(leaf[12..], mkfsId); - BinaryPrimitives.WriteUInt64LittleEndian(leaf[16..], 0); // flush id - BinaryPrimitives.WriteUInt16LittleEndian(leaf[24..], 0); // flags - leaf[26] = level; // 1 = leaf, 2 = twig - leaf[27] = 0; + BinaryPrimitives.WriteUInt16LittleEndian(node.AsSpan(2), checked((ushort)items.Count)); + BinaryPrimitives.WriteUInt16LittleEndian(node.AsSpan(4), checked((ushort)(blockSize - items.Count * ItemHeaderBytes - at))); + BinaryPrimitives.WriteUInt16LittleEndian(node.AsSpan(6), checked((ushort)at)); + BinaryPrimitives.WriteUInt32LittleEndian(node.AsSpan(8), unchecked((uint)NodeMagic)); + BinaryPrimitives.WriteUInt32LittleEndian(node.AsSpan(12), mkfsId); + node[26] = level; + return node; } } diff --git a/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Reiser4/Reiser4Writer.cs b/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Reiser4/Reiser4Writer.cs index 13d4832f0..fb91f8ea8 100644 --- a/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Reiser4/Reiser4Writer.cs +++ b/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Reiser4/Reiser4Writer.cs @@ -136,63 +136,11 @@ public sealed class Reiser4Writer { /// . public ulong BlockCount { get; set; } = MinBlockCount; - // ── Payload area (workbench-layout) ──────────────────────────────────────────── - // - // The reserved blocks above are byte-exact mkfs.reiser4 captures describing an - // empty tree, and the tree's own item plugins (extent40 bodies keyed by file - // offset, cde40 directory units) are not reproduced here. Files therefore live - // in a workbench-owned area past the reserved blocks, announced by a marker in - // the master superblock's spare region and described by a chained directory. - // - // The format's own accounting stays coherent: every block the payload occupies - // is marked allocated in the block-allocator bitmap and subtracted from - // sb_free_blocks, so an allocator walking the volume never hands the same block - // out twice. What this layout is NOT is a reiser4 storage tree, so the payload - // is invisible to any reader of the format — the same honest scope the - // workbench's OpenVMS and AmigaPFS writers declare. - // - // Measured, not assumed. reiser4progs builds from source without root, and its - // tools read an image on their own — no kernel driver exists to mount against: - // - // fsck.reiser4 --check -a -f volume.img → exits 0, the volume is well formed - // debugfs.reiser4 -k / volume.img → the root holds "." and ".." alone - // debugfs.reiser4 -t volume.img → the same two nodes a native volume - // has, item for item - // - // So what this writes is not a volume a reader can tell from an empty one that - // mkfs.reiser4 made — it is one. What it is not is a volume holding the files it - // was given, and that is the whole of the gap. - // - // What closing it takes, from the plugin sources rather than from memory. A leaf - // node is a 28-byte header — plugin id, item count, free space, free-space start, - // the magic 0x52344653, the mkfs id, a flush id, flags, level — then item bodies - // upward from 28 and an array of item headers growing downward from the node's - // end, each header a key, an offset, flags and a plugin id. A file needs three - // things put in that: a stat40 item carrying the light-weight, unix and - // plugin-set extensions the root's own already shows; an entry added to the - // root's cde40 item, keyed by the r5 hash of the name; and extent40 items keyed - // by offset for its blocks. The block allocator bitmap and the superblock's free - // count already move with the payload area and would move with these instead. - // - // A key is four little-endian words: the locality in the top sixty bits of the - // first with the item type in its low four, an ordering, the object id, and an - // offset. A file's stat data and its body share locality, ordering and object id - // and differ only in that type and in the offset. - // - // A directory entry's key carries the name itself rather than a hash of it, for - // any name of twenty-three characters or fewer — which is every name this writer - // produces. The bytes pack big-endian into the ordering starting one byte in, - // then into the object id, then into the offset; the top seven bits of the - // ordering hold a fibre, which is the last character of the name when the one - // before it is a dot and zero otherwise. Checked rather than assumed: packing - // ".." that way gives 0x2e2e0000000000, which is what a volume made by - // mkfs.reiser4 has on it. A name held in its key is stored nowhere else, so its - // entry is the twenty-four bytes of the target's key alone — which is the spacing - // a real volume shows between "." and "..". - // - // fsck.reiser4 gates it and debugfs.reiser4 -t reads back what was written, which - // is the same pair of tools that turned four supposed kernel limits in NILFS2 - // into four bugs of this project's own. + // Native stat40/cde40 items live in leaves; extent40 items live in twigs. + // Reiser4Tree packs additional nodes as needed, and allocator accounting covers + // both file data and tree blocks. Derived layout and oracle evidence are in + // docs/REISER4-NATIVE-PROFILE.md. The legacy constants below are retained only + // for reading images made by the earlier private-directory writer. /// Marker written at of the master superblock. /// The value spells nothing: a marker that reads as words names whoever chose them. @@ -223,40 +171,48 @@ public sealed class Reiser4Writer { /// internal const ulong BlocksPerBitmap = (ulong)(BlockSize - 4) * 8; - private readonly List<(string Name, FilePayload Payload)> _files = []; + private readonly List<(string Name, FilePayload Payload, Reiser4Reader.FileMetadata? Metadata)> _files = []; /// Adds a regular file to the payload area. - public void AddFile(string name, byte[] data) { + public void AddFile(string name, byte[] data, Reiser4Reader.FileMetadata? metadata = null) { ValidateName(name); ArgumentNullException.ThrowIfNull(data); - this._files.Add((name, FilePayload.FromBytes(data))); + this._files.Add((name, FilePayload.FromBytes(data), metadata)); } /// Adds a file whose bytes are pulled from as the image is written. - public void AddStreamingFile(string name, long size, Func openStream) { + public void AddStreamingFile(string name, long size, Func openStream, Reiser4Reader.FileMetadata? metadata = null) { ValidateName(name); ArgumentNullException.ThrowIfNull(openStream); - this._files.Add((name, FilePayload.FromStream(size, openStream))); + this._files.Add((name, FilePayload.FromStream(size, openStream), metadata)); } - /// - /// Why cannot be a root-directory entry of this profile, - /// or when it can. A slash would be written into a single - /// directory entry's name (reiser4 has no such entry), "." and ".." are the - /// directory's own entries, and a NUL ends the name early. - /// - internal static string? RejectName(string? name) => name switch { - null or "" => "an entry needs a name", - "." or ".." => $"'{name}' is reserved for the directory's own entries", - _ when name.AsSpan().IndexOfAny('/', '\\') >= 0 => $"'{name}' is nested; this writer only builds the root directory", - _ when name.Contains('\0') => $"'{name}' contains a NUL character", - _ => null, - }; + /// Validates the path components supported by the native namespace writer. + internal static string? RejectName(string? name) { + if (string.IsNullOrEmpty(name)) return "an entry needs a name"; + var components = name.Replace('\\', '/').Split('/'); + if (components.Length > 129) return "this profile supports at most 129 path components"; + if (components.Any(static component => component.Length == 0 || component is "." or "..")) + return "path components must be non-empty and cannot be dot entries"; + if (name.Contains('\0') || name.Contains(':') || name.Any(static c => c > 127)) + return "this profile requires ASCII names without NUL or colon"; + return null; + } + + private readonly Dictionary _directories = new(StringComparer.Ordinal); + + /// Retains an explicit directory, including an empty directory. + public void AddDirectory(string name, Reiser4Reader.FileMetadata? metadata = null) { + var normalized = name.Replace('\\', '/').TrimEnd('/'); + if (normalized.Length > 0 && RejectName(normalized) is { } reason) + throw new NotSupportedException("Reiser4: " + reason + "."); + this._directories[normalized] = metadata; + } private void ValidateName(string name) { if (RejectName(name) is { } reason) throw new NotSupportedException("Reiser4: " + reason + "."); - if (this._files.Any(f => f.Name.Equals(name, StringComparison.Ordinal))) + if (this._files.Any(f => f.Name.Replace('\\', '/').Equals(name.Replace('\\', '/'), StringComparison.Ordinal))) throw new ArgumentException($"Reiser4: '{name}' is already in the root directory.", nameof(name)); } @@ -294,6 +250,14 @@ public byte[] Build() { /// the end. public void Write(Stream output) { ArgumentNullException.ThrowIfNull(output); + if (!output.CanSeek) { + using var staged = Compression.Registry.RebuildVerb.CreateScratchStream(); + this.Write(staged); + staged.Position = 0; + staged.CopyTo(output); + output.Flush(); + return; + } var blocks = Math.Max(this.BlockCount, MinBlockCount); var totalBytes = checked((long)blocks * BlockSize); @@ -335,14 +299,13 @@ public void Write(Stream output) { BinaryPrimitives.WriteUInt32LittleEndian(blk23.AsSpan(NodeMkfsIdOff, 4), mkfsId); BinaryPrimitives.WriteUInt32LittleEndian(blk24.AsSpan(NodeMkfsIdOff, 4), mkfsId); - // ── Payload area: directory chain, then each file's data blocks ─────── - // Both step over the strided bitmap blocks, which the bitmap pass below then + // ── Native file data blocks ───────────────────────────────────────── + // Allocations step over the strided bitmap blocks, which the bitmap pass below then // marks along with everything else in use. var cursor = ReservedBlockCount; ulong Alloc() { while (IsBitmapBlock(cursor)) ++cursor; - if (cursor >= blocks) - throw new IOException($"Reiser4: a {blocks:N0}-block image has no room left for the payload."); + blocks = Math.Max(blocks, checked(cursor + 1)); return cursor++; } @@ -355,7 +318,7 @@ ulong Alloc() { // the tree — one file's bytes, described twice, sitting in one place. var treeFiles = new List(this._files.Count); var nextObjectId = RootObjectId + 1; - foreach (var (name, payload) in this._files) { + foreach (var (name, payload, metadata) in this._files) { var need = (payload.Size + BlockSize - 1) / BlockSize; var first = need > 0 ? Alloc() : 0UL; // A file's blocks are consecutive apart from any bitmap they straddle, so @@ -378,7 +341,7 @@ ulong Alloc() { } treeFiles.Add(new Reiser4Tree.Entry { - Name = name, ObjectId = nextObjectId++, Size = payload.Size, Runs = runs, + Name = name, ParentObjectId = RootObjectId, ObjectId = nextObjectId++, Size = payload.Size, Runs = runs, Metadata = metadata, }); } @@ -386,14 +349,22 @@ ulong Alloc() { // Every file gets a stat data and its extents, and the root directory an entry // naming it — so what the volume holds is what a reader of the format finds, // not only what our own reader knows to look for. - Reiser4Tree.Build(blk24, BlockSize, mkfsId, (uint)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), - RootLocality, RootObjectId, treeFiles); - Reiser4Tree.BuildTwig(blk23, BlockSize, mkfsId, RootObjectId, treeFiles); + var tree = Reiser4Tree.Build(blk24, BlockSize, mkfsId, (uint)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), + RootLocality, RootObjectId, treeFiles, this._directories, Alloc); + blk23 = tree.Nodes[23]; + blk24 = tree.Nodes[24]; + BinaryPrimitives.WriteUInt16LittleEndian(blk17.AsSpan(F40TreeHeightOff), tree.Height); + BinaryPrimitives.WriteUInt64LittleEndian(blk17.AsSpan(F40OidNextOff), tree.NextObjectId); + BinaryPrimitives.WriteUInt64LittleEndian(blk17.AsSpan(F40OidFileCountOff), tree.ObjectCount); + totalBytes = checked((long)blocks * BlockSize); + BinaryPrimitives.WriteUInt64LittleEndian(blk17.AsSpan(F40BlockCountOff), blocks); + BinaryPrimitives.WriteUInt64LittleEndian(blk22.AsSpan(BackupF40BlockCountOff), blocks); var usedBlocks = cursor; // every block below the cursor is reserved or payload // ── Free-block accounting and the bitmap blocks ────────────────────── - BinaryPrimitives.WriteUInt64LittleEndian(blk17.AsSpan(F40FreeBlocksOff, 8), blocks - usedBlocks); + var tailBitmapBlocks = (blocks - 1) / BlocksPerBitmap - (usedBlocks - 1) / BlocksPerBitmap; + BinaryPrimitives.WriteUInt64LittleEndian(blk17.AsSpan(F40FreeBlocksOff, 8), blocks - usedBlocks - tailBitmapBlocks); var bitmaps = BuildBitmaps(blocks, usedBlocks, blk18); // ── Emit the metadata prefix, then the payload ─────────────────────── @@ -415,18 +386,17 @@ ulong Alloc() { } output.Flush(); - if (!output.CanSeek) { - for (var b = firstDataBlock; b < blocks; b++) - output.Write(zero, 0, BlockSize); - return; - } - output.SetLength(basePosition + totalBytes); foreach (var (block, bytes) in bitmaps) { if (block < firstDataBlock) continue; // already emitted in the prefix output.Position = basePosition + (long)block * BlockSize; output.Write(bytes); } + foreach (var (block, node) in tree.Nodes) { + if (block is 23 or 24) continue; + output.Position = basePosition + checked((long)block * BlockSize); + output.Write(node); + } payloads.FlushTo(output, basePosition); output.Position = basePosition + totalBytes; output.Flush(); diff --git a/Hawkynt.FileFormats.FileSystems/README.md b/Hawkynt.FileFormats.FileSystems/README.md index 6ab6077ec..92c53f241 100644 --- a/Hawkynt.FileFormats.FileSystems/README.md +++ b/Hawkynt.FileFormats.FileSystems/README.md @@ -333,7 +333,7 @@ cwb recover raw.img --mode files --format Jpeg,Png - Where **Notes** says a format's edits *rebuild* the volume, an add or remove costs the whole image rather than the bytes that changed, and the block placement of untouched files may move. - **bcachefs** creation, in-place add/replace, reading and extraction preserve inode timestamps (to 100 ns, before the epoch and past 2038), UID/GID and permission bits when supplied through the archive-input metadata; ordinary file-backed inputs capture available host timestamps and mode automatically. Applying UID/GID on extraction depends on host APIs and is not available through the portable .NET filesystem API. Volumes are written at metadata version 1.3, the newest the distributions' bcachefs-tools open, so their `fsck` can judge them; newer tools upgrade such a volume as they open it. Mutation is profile-gated: the in-place writer owns single-device regular pointer extents as emitted by this package, and re-publishes every tree from what it read, so a volume another implementation wrote (packed keys, appended bsets, a journal with keys in it, another metadata version) is refused for mutation with the reason and left untouched; it is still read, including packed and multi-bset nodes, sparse files and symbolic links. Compressed and encrypted extents are recognised and refused per file, not decoded. Bucket generations are carried through a mutation: emptying a bucket advances its generation, and the alloc key, the `bucket_gens` slot, the extent pointer and its backpointer are written from one source and checked against each other before the commit is published. Foreign volumes with extra live b-trees, non-canonical `bucket_gens` or `alloc_v4` keys, inline / reflink / compressed extent keys, or unsupported inode and dirent types are refused for mutation rather than rewritten speculatively; read support is broader. What a volume carries and how each value was established is written up in [BCACHEFS-ON-DISK.md](https://github.com/Hawkynt/CompressionWorkbench/blob/main/docs/BCACHEFS-ON-DISK.md). - **ReFS** edits are offline-quiescent: the image must not be mounted, and `RefsMutationMode.NativeCow` stays fail-closed. The reader walks ReFS 3.x containers, MSB+ trees, allocators, Block Refcount rows and MLog framing; the offline editor relocates data and metadata pages, replaces and removes regular files and empty directories through CoW B+ replacement and alternate-checkpoint publication. Block Refcount row creation and whole-file clone semantics are implemented and tested: `RefsOfflineBlockCloner.CloneWholeFile` repoints an existing destination stream at an existing, equal-sized, cluster-aligned source stream, materialises the shared-cluster reference counts — including the first clone of a slot the sparse table does not yet track — and publishes Object Table root #0 and Block Refcount root #6 through one alternate checkpoint, reclaiming the destination's former allocation only once that checkpoint is durable. Writing to a clone afterwards reallocates rather than overwriting the shared clusters. **No public verb reaches this primitive**: it is a library entry point, not something `cwb` can be asked to do, and the profile is deliberately narrower than `FSCTL_DUPLICATE_EXTENTS_TO_FILE` — resident, sparse, integrity, zero-length, already-shared and partial-range clones are all refused. Still open before a mounted driver could use the same core: redo-payload codecs and replay for every opcode, every Schema Table key rule, the allocation-zone policies of the three allocators, container create / delete / move, partial-range and EOF-extending clones, hard links, sparse and integrity-stream mutation beyond 4 KiB clusters, snapshots, ADS, security descriptors, reparse points, USN, rename, truncate, a format path, locking and a Windows `chkdsk` corpus. -- **Reiser4** R/W is a narrow native profile: regular files in the root directory, their stat data and the directory in a single leaf, their extents in the single twig above it, exactly where reiser4progs looks for them. `fsck.reiser4` calls every volume consistent and `debugfs.reiser4` lists the files after create, streamed create, rebuild add/replace/remove and both defrag directions. Sub-directories, more files than one 4 KiB leaf holds, tail items and per-file metadata beyond size are refused or not represented; images from before the native writer, whose files sit in a private payload chain, are still read. +- **Reiser4** R/W supports native node40 trees with multiple leaves and internal levels, nested and empty directories, and twig extent items. Rebuild edits preserve object IDs, mode, UID/GID, timestamps, volume identity and opaque stat-data extensions; directory accounting follows namespace changes. `UpdateMetadata` applies Unix fields through a staged rebuild. A 5,000-file tree with three levels and nested directories passes `fsck.reiser4`, including metadata updates, add/remove and defrag. The existing two-node flat profile retains in-place defrag in both directions; larger trees use staged consolidation at the start. Separate xattr items, symlinks, sparse/shared files, non-ASCII names and alternate node/item plugins remain outside this profile. Unsupported trees are refused before rebuild, and older private payload-chain images remain readable. See [native profile notes](../docs/REISER4-NATIVE-PROFILE.md). - **GFS2** R/W is the standalone `lock_nolock` profile with regular files in the root directory; small files are stuffed and larger files use the indirect tree. Existing-image edits rebuild the volume while preserving its size floor, lock-table value, UUID and each regular file's mode, owner, timestamps (with nanoseconds) and access-policy flags; layout-changing flags such as JDATA are not carried over because the rebuilt file is laid out plainly. Directory attributes are not preserved. ExHash/nested-directory writing, journal replay, cluster-lock-manager state and extended attributes remain out of scope. - **NSS** native read support is intentionally narrow and quiescent: the reader reconstructs validated DirH/LEAF namespace relationships and ordinary contiguous file extents using the OES-derived clean-room profile in [NSS-ON-DISK.md](https://github.com/Hawkynt/CompressionWorkbench/blob/main/docs/NSS-ON-DISK.md), while retaining anchor diagnostics for unsupported images. Native allocation trees, journals, trustee metadata, ZLBH history, compressed/sparse/fragmented data profiles and writable NSS pools remain out of scope; the existing writer still emits a private Workbench container rather than a pool NetWare/OES would mount. - **HFS+** is listed as a POSIX system shows it: a catalog '/' appears as ':' (and a ':' given to the writer is stored as '/'), file hard links carry their indirect node's data under every name, and the metadata directories and journal files are left out. Directory hard links are not resolved, so a non-empty `.HFS+ Private Directory Data` folder stays visible. Every decmpfs (HFS+ transparent compression) method is read: 1, 3/4 zlib, 5 (zeros, as libfshfs reads it), 7/8 LZVN, 9/10 raw, 11/12 LZFSE and 13/14 LZBITMAP; a dataless placeholder (0x80000001/0x80000002) has no local content and lists and extracts as empty, with `Kind` `dataless`. The writer's `Compression` option (default `None`) stores files compressed with any of those methods except 5 and the dataless types, which do not encode given content: inline in the attribute when the encoding fits (3802 bytes, files up to 64 KiB), otherwise as 64 KiB resource-fork chunks; a file that would not shrink stays uncompressed. libfshfs reads the zlib, LZVN, LZFSE and raw volumes byte for byte; it implements neither method 1 nor LZBITMAP, so those two have no independent HFS+ reader here (the LZBITMAP stream itself is checked against dissect.util). In-place add writes uncompressed files only. Removing a file in place frees every extent of both forks; removing or replacing a hard link, a file with extended attributes or transparent compression, or a fork continued in the extents overflow file is refused, as is a removal that would change the first key of a catalog leaf or empty it (the B-tree is not rebalanced in place). On extraction, characters the host cannot hold in a file name (on Windows `: < > " | ? *`, control characters, trailing dots and blanks) become `_`, as for every format. diff --git a/docs/REISER4-NATIVE-PROFILE.md b/docs/REISER4-NATIVE-PROFILE.md new file mode 100644 index 000000000..6df773c89 --- /dev/null +++ b/docs/REISER4-NATIVE-PROFILE.md @@ -0,0 +1,86 @@ +# Reiser4 native profile + +## Evidence and provenance + +The existing binary templates were captured from `mkfs.reiser4 -fffy` in +reiser4progs 1.2.2. Their fixed fields are the starting point for this profile. +The on-disk layout is also described by the Reiser4 node40, stat40, cde40, +extent40 and format40 plugins in the upstream +[reiser4progs project](https://github.com/edward6/reiser4progs). +The upstream GPL implementation is reference material, not copied or converted +source. This follows sourcing rung 2: no compatible managed implementation was +identified. The new traversal and rebuild tests use synthetic native nodes; +reference-tool conformance is covered separately as described below. Mounted +kernel interoperability has not been tested. + +## Derived layout used here + +For the captured 4096-byte format40 profile, the master is block 16 and the +format superblock is block 17. The latter stores the root block at byte 16 and +the tree height at byte 68. The root object's stat key has locality 41 and object +ID 42. Names beneath a directory use that directory's object ID as locality. + +A node40 block has a 28-byte header, bodies growing upwards and 38-byte item +headers growing downwards from the end. The header's item count is at byte 2, +body-end offset at byte 6, magic at byte 8 and level at byte 26. Each item +header contains four little-endian key words, followed by body offset, flags +and plugin ID. Internal pointer bodies contain a little-endian block address. +Traversal follows all pointers, requires descending levels, and rejects cycles +and out-of-range addresses. + +The supported object plugins are stat40 (0), cde40 (2) and extent40 (5). +Stat-data and directory items occupy level-1 leaves. Extents occupy level-2 +twig nodes alongside child pointers; the reader also accepts the earlier +workbench placement of extents in leaves. This placement follows upstream +`libreiser4/tree.c` target-level selection, which assigns extent items to the +twig level and other object items to the leaf level. +Stat-data must include the lightweight and Unix extensions. Its first 44 bytes +carry extension mask, mode, link count, size, UID, GID, three timestamps and +allocated-byte count. Additional stat-data bytes are retained opaquely. +Extent bodies contain pairs of 64-bit block start and block count. Multiple +items are joined in logical-offset order only when they describe contiguous +logical content. Holes and unsupported body plugins are rejected. + +Cde40 has a 16-bit entry count, 26-byte unit headers, and units containing a +24-byte target stat key plus the stored name when its key is hashed. Resolving +the target's locality and object ID distinguishes files and directories. Dot +entries are omitted from the archive namespace. Nested and empty directories +are represented explicitly. Large directories split at unit boundaries; each +item uses its first unit name key. Extent arrays split at run boundaries with +logical byte offsets. Leaves end before a twig extent key, so a child pointer does not span an +extent key range at its parent. Sorted leaf items pack into bounded leaves, and parent nodes +use the first key of each child as their delimiting key. Parent levels grow until +one root fits. Format40 height, allocator bitmaps, object counts and allocated +node blocks follow the resulting layout. Paths have at most 129 components to +bound directory recursion. This writer restricts names to ASCII; it rejects +unsupported names rather than truncating them into different names. + +## Rebuild contract and limits + +Add, remove, defrag, shrink and layout rebuild capture metadata before staging. +Existing stat-data extension bytes survive. Mode, ownership, link count and +timestamps are retained or applied through the typed metadata fields; size and +allocated-byte fields follow the rebuilt layout. `UpdateMetadata` stages explicit +metadata changes, including changes to the root. Existing object IDs survive, +and new IDs are allocated beyond the retained IDs rather than renumbering live +objects. Verification includes identity and typed fields as well as extension +bytes. Volume UUID and label, root stat-data extensions, +and empty directories are retained. Edits and defrag stage the target and verify +its complete native namespace and retained stat-data before replacing the source. +Unsupported or incomplete native trees are refused before editing. Shrink copies +the original through if a supported smaller result cannot be built. + +Reading supports multiple internal levels. Writing builds multiple leaves and additional internal levels as needed. An +indivisible stat-data body or directory unit larger than a node is refused. This is not full Reiser4 metadata support: +separate xattr items, arbitrary object plugins, symlinks, sparse files, shared +objects, non-ASCII names and alternate node plugins are outside the profile. +Opaque stat-data retention does not prove the semantics of unknown extensions. +Native tree blocks discovered by traversal are reserved in the wipe map. + +Validation uses reiser4progs 1.2.2 (Ubuntu package 1.2.2-1build2): native images +with 1, 100 and 5000 files in nested directories plus empty directories were +accepted by `fsck.reiser4 -y` as consistent. The largest has 166 leaves and +three tree levels. `Reiser4MutationExternalTests` additionally checks a wide +nested tree after Unix metadata updates, add/remove and defrag against +`fsck.reiser4` and `debugfs.reiser4`. Synthetic tests exercise stat-data tails +without claiming their unknown extension semantics are validated by the oracle. From a87fd2a7970c1d41f4bd32e5612c05559f9cae17 Mon Sep 17 00:00:00 2001 From: Hawkynt Date: Fri, 9 Oct 2026 11:27:35 +0200 Subject: [PATCH 2/7] # Reiser4 rebuilds keep the mkfs id and refuse volumes of another plugin profile Every Reiser4 edit, defrag, shrink and relayout gave the volume a new mkfs id. A volume made by mkfs.reiser4 with a non-default profile (-o hash=tea_hash, formatting=tails, fibration=lexic_fibre) came out of any edit failing fsck.reiser4 ("The Plugin Set slot N of the root directory ... does not match the backed up value"), and with the tea hash a long name the edit added could not be found by debugfs.reiser4. The rebuild writes the captured mkfs prefix again with only UUID and label patched in: the mkfs id was drawn at random, the format40 tail policy and the backup record's copy of the root plugin set came from the default template, while the root stat data (with the source's real plugin set) was carried over, and names were keyed with r5 regardless of the volume's hash. The mkfs id is now carried like UUID and label. Before staging anything, the source's fixed blocks are compared with what the writer would write back: master, format40 (magic, tail policy, flags, version), the journal header and footer (must be blank - a kernel journal is not replayed), the status block (must say consistent) and the backup record outside its identity fields. Any difference is NotSupportedException with the image untouched; such volumes are still read. Tests: the mkfs id survives add, remove and defrag; a patched tail policy, backup hash slot, status block or journal header is refused by add, remove, defrag and relayout with the image byte-identical, and shrink copies it through; the tree grows 2 -> 3 levels past one twig of extents and shrinks back on removal; the last file of a directory leaves an empty directory; names of 23, 24 and 255 characters round-trip. Reiser4ProfileExternalTests (WSL-gated) checks against reiser4progs 1.2.2: an mkfs.reiser4 volume after adding nested files, long names and an empty directory is consistent with UUID, label, mkfs id, policies and root stat unchanged; the three non-default profiles are refused byte-identical; 12,000 files make a consistent height-4 tree; a tree fsck --build-fs rewrote (lost+found, object 0xffff, uid 1000) is read and edited keeping its key and owner; a large-time stat extension survives add and defrag (sdext_lt still shown). File bytes are taken from the blocks debugfs.reiser4 -i names, since debugfs -k prints file contents through printf. Sourcing rung 2: reiser4progs (GPL) as black-box oracle only; the fixed-block fields were located by diffing mkfs.reiser4 output across profiles. --- .../Reiser4/Reiser4NativeTreeTests.cs | 125 +++++++++ .../Reiser4/Reiser4ProfileExternalTests.cs | 257 ++++++++++++++++++ .../Reiser4FormatDescriptor.cs | 34 ++- .../FileSystem.Reiser4/Reiser4Writer.cs | 51 +++- 4 files changed, 457 insertions(+), 10 deletions(-) create mode 100644 Compression.Tests/Reiser4/Reiser4ProfileExternalTests.cs diff --git a/Compression.Tests/Reiser4/Reiser4NativeTreeTests.cs b/Compression.Tests/Reiser4/Reiser4NativeTreeTests.cs index 1faccd278..be1a3bc02 100644 --- a/Compression.Tests/Reiser4/Reiser4NativeTreeTests.cs +++ b/Compression.Tests/Reiser4/Reiser4NativeTreeTests.cs @@ -395,4 +395,129 @@ public void Writer_PlacesExtentsInTwigNodes() { Assert.That(reader.Extract(reader.Entries.Single()), Is.EqualTo(Payload(9_137, 15))); } + private static uint MkfsIdOf(byte[] image) + => BinaryPrimitives.ReadUInt32LittleEndian(image.AsSpan(17 * Reiser4Writer.BlockSize + 48)); + + private static ushort HeightOf(byte[] image) + => BinaryPrimitives.ReadUInt16LittleEndian(image.AsSpan(17 * Reiser4Writer.BlockSize + 68)); + + [Test, Category("RoundTrip")] + public void GivenAVolume_WhenEditingAndDefragmentingByRebuild_ThenTheMkfsIdSurvivesWithUuidAndLabel() { + var writer = new Reiser4Writer { Label = "keepme", MkfsId = 0x1234ABCDu, Uuid = Enumerable.Range(1, 16).Select(static i => (byte)i).ToArray() }; + writer.AddFile("a/b.bin", Payload(5_000, 30)); + using var image = new MemoryStream(writer.Build()); + var descriptor = new Reiser4FormatDescriptor(); + descriptor.Add(image, [ArchiveInputInfo.InMemory("a/c.bin", Payload(9_000, 31))]); + Assert.That(MkfsIdOf(image.ToArray()), Is.EqualTo(0x1234ABCDu), "after add"); + descriptor.Remove(image, ["a/b.bin"]); + Assert.That(MkfsIdOf(image.ToArray()), Is.EqualTo(0x1234ABCDu), "after remove"); + descriptor.Defragment(image); + Assert.That(MkfsIdOf(image.ToArray()), Is.EqualTo(0x1234ABCDu), "after defrag"); + using var reader = new Reiser4Reader(image); + Assert.Multiple(() => { + Assert.That(reader.Label, Is.EqualTo("keepme")); + Assert.That(reader.UuidHex, Is.EqualTo(Convert.ToHexString(writer.Uuid!))); + }); + } + + // Offsets inside the fixed blocks that carry the profile rather than the identity: + // the format40 tail policy (block 17, byte 70), the backup of the root directory's + // plugin set (block 22, from byte 127 on), the status block (21) and the journal (19, 20). + [TestCase(17, 70, (byte)1, TestName = "GivenATailsOnlyPolicy_WhenEditing_ThenTheEditIsRefusedAndTheImageIsUntouched")] + [TestCase(22, 131, (byte)2, TestName = "GivenANonDefaultHashInTheBackedUpPluginSet_WhenEditing_ThenTheEditIsRefusedAndTheImageIsUntouched")] + [TestCase(21, 16, (byte)2, TestName = "GivenAStatusBlockNotMarkedConsistent_WhenEditing_ThenTheEditIsRefusedAndTheImageIsUntouched")] + [TestCase(19, 0, (byte)1, TestName = "GivenAJournalHeaderThatIsNotBlank_WhenEditing_ThenTheEditIsRefusedAndTheImageIsUntouched")] + public void VolumeOutsideTheWriterProfile_IsRefusedForEveryRebuild(int block, int offset, byte value) { + var writer = new Reiser4Writer(); + writer.AddFile("d/f.bin", Payload(3_000, 32)); + var original = writer.Build(); + original[block * Reiser4Writer.BlockSize + offset] = value; + var descriptor = new Reiser4FormatDescriptor(); + using var image = new MemoryStream(original.ToArray()); + + Assert.Multiple(() => { + Assert.Throws(() => descriptor.Add(image, [ArchiveInputInfo.InMemory("x.bin", Payload(10, 33))])); + Assert.That(image.ToArray(), Is.EqualTo(original), "add"); + Assert.Throws(() => descriptor.Remove(image, ["d/f.bin"])); + Assert.That(image.ToArray(), Is.EqualTo(original), "remove"); + Assert.Throws(() => descriptor.Defragment(image)); + Assert.That(image.ToArray(), Is.EqualTo(original), "defragment"); + using var relayout = new MemoryStream(); + Assert.Throws(() => descriptor.RebuildStreaming(image, relayout, new LayoutRebuildOptions())); + Assert.That(image.ToArray(), Is.EqualTo(original), "relayout source"); + using var shrunk = new MemoryStream(); + descriptor.Shrink(image, shrunk); + Assert.That(shrunk.ToArray(), Is.EqualTo(original), "shrink copies a volume it cannot rebuild through unchanged"); + }); + } + + [Test, Category("Boundary")] + public void GivenOneFile_WhenWriting_ThenTheTreeIsTheTwoLevelTreeMkfsWrites() { + var writer = new Reiser4Writer(); + writer.AddFile("one.bin", Payload(4_097, 36)); + var image = writer.Build(); + Assert.That(HeightOf(image), Is.EqualTo(2)); + } + + [Test, Category("Boundary")] + public void GivenMoreTwigExtentsThanOneTwigHolds_WhenWriting_ThenTheTreeGrowsAThirdLevelAndEveryFileReadsBack() { + // A twig item is a 38-byte header and a 16-byte extent unit: one 4 KiB twig holds + // fewer than 76 of them, so 200 single-run files cannot share the root. + var writer = new Reiser4Writer(); + for (var i = 0; i < 200; ++i) writer.AddFile($"f{i:D3}", Payload(1 + i, i)); + using var image = new MemoryStream(writer.Build()); + using var reader = new Reiser4Reader(image); + Assert.Multiple(() => { + Assert.That(HeightOf(image.ToArray()), Is.EqualTo(3)); + Assert.That(reader.NativeTreeValid, Is.True); + Assert.That(reader.Entries, Has.Count.EqualTo(200)); + foreach (var entry in reader.Entries) + Assert.That(reader.Extract(entry), Is.EqualTo(Payload(1 + int.Parse(entry.Name[1..]), int.Parse(entry.Name[1..]))), entry.Name); + }); + } + + [Test, Category("Boundary")] + public void GivenATallTree_WhenRemovingAllButOneFile_ThenTheRebuildShrinksBackToTwoLevels() { + var writer = new Reiser4Writer(); + for (var i = 0; i < 200; ++i) writer.AddFile($"dir/f{i:D3}", Payload(10, i)); + using var image = new MemoryStream(writer.Build()); + Assert.That(HeightOf(image.ToArray()), Is.EqualTo(3)); + new Reiser4FormatDescriptor().Remove(image, [.. Enumerable.Range(1, 199).Select(static i => $"dir/f{i:D3}")]); + using var reader = new Reiser4Reader(image); + Assert.Multiple(() => { + Assert.That(HeightOf(image.ToArray()), Is.EqualTo(2)); + Assert.That(reader.Entries.Select(static e => e.Name), Is.EquivalentTo(new[] { "dir", "dir/f000" })); + Assert.That(reader.Extract(reader.Entries.Single(static e => !e.IsDirectory)), Is.EqualTo(Payload(10, 0))); + }); + } + + [Test, Category("Boundary")] + public void GivenTheOnlyFileOfADirectory_WhenRemovingIt_ThenTheDirectoryStaysAsAnEmptyDirectory() { + var writer = new Reiser4Writer(); + writer.AddFile("keep/only.bin", Payload(10, 37)); + writer.AddFile("other.bin", Payload(10, 38)); + using var image = new MemoryStream(writer.Build()); + new Reiser4FormatDescriptor().Remove(image, ["keep/only.bin"]); + using var reader = new Reiser4Reader(image); + Assert.That(reader.Entries.Select(static e => (e.Name, e.IsDirectory)), + Is.EquivalentTo(new[] { ("keep", true), ("other.bin", false) })); + } + + [TestCase(23, TestName = "GivenANameOfTwentyThreeCharacters_WhenWriting_ThenItIsHeldInTheKeyAndReadsBack")] + [TestCase(24, TestName = "GivenANameOfTwentyFourCharacters_WhenWriting_ThenItIsHashedAndReadsBack")] + [TestCase(255, TestName = "GivenANameOf255Characters_WhenWriting_ThenItReadsBack")] + [Category("Boundary")] + public void NameLengthBoundaries_RoundTrip(int length) { + var name = new string((char)('a' + length % 26), length); + var writer = new Reiser4Writer(); + writer.AddFile("n/" + name, Payload(length, length)); + using var image = new MemoryStream(writer.Build()); + using var reader = new Reiser4Reader(image); + var entry = reader.Entries.Single(static e => !e.IsDirectory); + Assert.Multiple(() => { + Assert.That(entry.Name, Is.EqualTo("n/" + name)); + Assert.That(reader.Extract(entry), Is.EqualTo(Payload(length, length))); + }); + } + } diff --git a/Compression.Tests/Reiser4/Reiser4ProfileExternalTests.cs b/Compression.Tests/Reiser4/Reiser4ProfileExternalTests.cs new file mode 100644 index 000000000..f27db2821 --- /dev/null +++ b/Compression.Tests/Reiser4/Reiser4ProfileExternalTests.cs @@ -0,0 +1,257 @@ +using System.Buffers.Binary; +using System.Globalization; +using System.Text.RegularExpressions; +using Compression.Registry; +using FileSystem.Reiser4; + +namespace Compression.Tests.Reiser4; + +/// +/// The multi-level writer and the metadata-carrying rebuilds, judged by reiser4progs: +/// fsck.reiser4 --check must call each volume consistent, debugfs.reiser4 -s +/// reports the tree height and the volume's identity, and file bytes are taken where +/// debugfs.reiser4 -i says they are — reiser4progs resolves the path to its stat data +/// and extent items, and the test only copies the blocks it names. (debugfs.reiser4 -k +/// cannot be the byte oracle: it passes file contents to printf as the format string.) +/// No Reiser4-capable kernel is reachable, so volumes the tools themselves wrote come from +/// mkfs.reiser4 and from fsck.reiser4 --build-fs, which inserts a lost+found. +/// +[TestFixture] +[Category("ExternalFsInterop")] +public sealed class Reiser4ProfileExternalTests { + + private string _tmpDir = null!; + + [SetUp] + public void SetUp() { + this._tmpDir = Path.Combine(Path.GetTempPath(), "cwb_r4prof_" + Guid.NewGuid().ToString("N")[..8]); + Directory.CreateDirectory(this._tmpDir); + } + + [TearDown] + public void TearDown() { + try { Directory.Delete(this._tmpDir, recursive: true); } catch { /* best effort */ } + } + + private static void RequireReiser4Progs() { + if (!FsInteropToolbox.WslAvailable && OperatingSystem.IsWindows()) + Assert.Ignore("WSL is not available on this machine."); + foreach (var tool in new[] { "mkfs.reiser4", "fsck.reiser4", "debugfs.reiser4" }) + if (!FsInteropToolbox.WslHasTool(tool)) + Assert.Ignore($"reiser4progs is not installed ('{tool}' missing; `sudo apt install -y reiser4progs`)."); + } + + private static byte[] Payload(int length, int seed) { + var bytes = new byte[length]; + new Random(seed).NextBytes(bytes); + return bytes; + } + + private string Save(string name, byte[] image) { + var path = Path.Combine(this._tmpDir, name); + File.WriteAllBytes(path, image); + return path; + } + + /// A volume made by mkfs.reiser4 itself, with extra options. + private byte[] Mkfs(string label, string options) { + var path = Path.Combine(this._tmpDir, label + ".mkfs.img"); + var wsl = FsInteropToolbox.WinToWsl(path); + var run = FsInteropToolbox.RunWsl($"truncate -s 64M {wsl} && mkfs.reiser4 -fffy -L {label} {options} {wsl}"); + Assert.That(run.ExitCode, Is.EqualTo(0), $"mkfs.reiser4 failed:\n{run.StdOut}\n{run.StdErr}"); + return File.ReadAllBytes(path); + } + + /// fsck.reiser4 --check on a copy, so the status it records does not touch the image under test. + private static void AssertFsckConsistent(string path) { + var wsl = FsInteropToolbox.WinToWsl(path); + var run = FsInteropToolbox.RunWsl($"cp {wsl} {wsl}.fsck && fsck.reiser4 --check -y {wsl}.fsck; rc=$?; rm -f {wsl}.fsck; exit $rc"); + var text = run.StdOut + "\n" + run.StdErr; + Assert.That(run.ExitCode, Is.EqualTo(0), $"fsck.reiser4 rejected {Path.GetFileName(path)}:\n{text}"); + Assert.That(text, Does.Contain("FS is consistent"), $"fsck.reiser4 did not call {Path.GetFileName(path)} consistent:\n{text}"); + } + + /// The superblock fields debugfs.reiser4 -s prints, by name. + private static Dictionary Super(string path) { + var run = FsInteropToolbox.RunWsl($"debugfs.reiser4 -s {FsInteropToolbox.WinToWsl(path)} (StringComparer.Ordinal); + foreach (var line in run.StdOut.Split('\n')) { + var colon = line.IndexOf(':'); + if (colon <= 0) continue; + fields.TryAdd(line[..colon].Trim(), line[(colon + 1)..].Trim()); + } + return fields; + } + + /// The directory listing debugfs.reiser4 -k prints: one "[stat key] name" line per entry. + private static string[] Listing(string path, string directory) { + var run = FsInteropToolbox.RunWsl($"debugfs.reiser4 -k '{directory}' {FsInteropToolbox.WinToWsl(path)} l.Trim()).Where(static l => l.StartsWith('['))]; + } + + /// The owner, mode and timestamps debugfs.reiser4 -i prints for an object. + private static string[] Ownership(string path, string member) { + var run = FsInteropToolbox.RunWsl($"debugfs.reiser4 -i '{member}' {FsInteropToolbox.WinToWsl(path)} l.Trim()) + .Where(static l => Regex.IsMatch(l, @"^(mode|uid|gid|atime|mtime|ctime):"))]; + } + + private static readonly Regex Run = new(@"(\d+)\((\d+)\)", RegexOptions.Compiled); + + /// A file's bytes from the blocks reiser4progs' own lookup names for it. + private static byte[] ReadThroughProgs(string path, string member) { + var run = FsInteropToolbox.RunWsl($"debugfs.reiser4 -i '/{member}' {FsInteropToolbox.WinToWsl(path)} l.StartsWith("UNITS=", StringComparison.Ordinal))) + foreach (Match unit in Run.Matches(line[line.IndexOf('[')..])) { + var start = long.Parse(unit.Groups[1].Value, CultureInfo.InvariantCulture); + var width = long.Parse(unit.Groups[2].Value, CultureInfo.InvariantCulture); + bytes.Write(image, checked((int)(start * Reiser4Writer.BlockSize)), checked((int)(width * Reiser4Writer.BlockSize))); + } + Assert.That(bytes.Length, Is.GreaterThanOrEqualTo(size), $"the extents reiser4progs found for /{member} are shorter than its size"); + return bytes.ToArray()[..checked((int)size)]; + } + + [Test, Category("HappyPath")] + public void GivenAVolumeMkfsWrote_WhenAddingNestedFilesLongNamesAndAnEmptyDirectory_ThenFsckPassesItsIdentityStaysAndTheProgsReadOurBytes() { + RequireReiser4Progs(); + var original = this.Mkfs("cwbmkfs", ""); + var files = new Dictionary { + ["top.bin"] = Payload(4_096, 1), + ["nested/deeper/edge.bin"] = Payload(4_097, 2), + ["nested/a-name-longer-than-twenty-three.txt"] = Payload(4_095, 3), + ["nested/" + new string('z', 255)] = Payload(1, 4), + }; + using var image = new MemoryStream(); + image.Write(original); + var descriptor = new Reiser4FormatDescriptor(); + descriptor.Add(image, [.. files.Select(static f => ArchiveInputInfo.InMemory(f.Key, f.Value)), + new ArchiveInputInfo("", "empty/inner", IsDirectory: true)]); + var edited = this.Save("added.img", image.ToArray()); + var originalPath = this.Save("original.img", original); + var before = Super(originalPath); + var after = Super(edited); + + AssertFsckConsistent(edited); + Assert.Multiple(() => { + foreach (var field in new[] { "uuid", "label", "mkfs id", "tail policy", "key policy", "blksize" }) + Assert.That(after[field], Is.EqualTo(before[field]), field); + foreach (var (name, data) in files) + Assert.That(ReadThroughProgs(edited, name), Is.EqualTo(data), name); + Assert.That(Listing(edited, "/empty/inner").Length, Is.EqualTo(2), "an empty directory holds only its dot entries"); + Assert.That(Ownership(edited, "/"), Is.EqualTo(Ownership(originalPath, "/")).And.Not.Empty, "the root directory mkfs wrote"); + }); + } + + [TestCase("-o hash=tea_hash", TestName = "GivenAVolumeMkfsWroteWithTheTeaHash_WhenAdding_ThenTheEditIsRefusedAndTheImageIsUntouched")] + [TestCase("-o formatting=tails", TestName = "GivenAVolumeMkfsWroteWithATailsOnlyPolicy_WhenAdding_ThenTheEditIsRefusedAndTheImageIsUntouched")] + [TestCase("-o fibration=lexic_fibre", TestName = "GivenAVolumeMkfsWroteWithLexicographicFibration_WhenAdding_ThenTheEditIsRefusedAndTheImageIsUntouched")] + public void VolumeWithAnotherPluginProfile_IsReadButNotRewritten(string options) { + RequireReiser4Progs(); + var original = this.Mkfs("cwbprof", options); + using var image = new MemoryStream(); + image.Write(original); + using (var reader = new Reiser4Reader(image)) + Assert.That(reader.NativeTreeValid, Is.True, "an empty volume of another profile is still read"); + var error = Assert.Throws(() => new Reiser4FormatDescriptor().Add(image, + [ArchiveInputInfo.InMemory("a-name-longer-than-twenty-three.txt", Payload(100, 5))])); + Assert.That(error!.Message, Does.Contain("refusing")); + Assert.That(image.ToArray(), Is.EqualTo(original)); + } + + [Test, Category("Boundary")] + public void GivenTwelveThousandFiles_WhenWriting_ThenFsckAcceptsAFourLevelTreeAndTheProgsReadTheBoundaryFiles() { + RequireReiser4Progs(); + var sizes = new Dictionary { + ["t/f000000"] = 1, ["t/f000001"] = 4_095, ["t/f000002"] = 4_096, ["t/f000003"] = 4_097, ["t/f011999"] = 2_000_000, + }; + var writer = new Reiser4Writer(); + for (var i = 0; i < 12_000; ++i) { + var name = $"t/f{i:D6}"; + writer.AddFile(name, Payload(sizes.GetValueOrDefault(name, 1 + i % 5_000), i)); + } + var path = this.Save("tall.img", writer.Build()); + + AssertFsckConsistent(path); + var super = Super(path); + Assert.Multiple(() => { + Assert.That(super["tree height"], Is.EqualTo("4")); + Assert.That(super["file count"], Is.EqualTo("12002"), "the files, t and the root"); + foreach (var (name, size) in sizes) + Assert.That(ReadThroughProgs(path, name), Is.EqualTo(Payload(size, int.Parse(name[3..], CultureInfo.InvariantCulture))), name); + }); + } + + [Test, Category("HappyPath")] + public void GivenAVolumeFsckRebuiltWithALostAndFound_WhenReadingAndAddingIntoIt_ThenItsObjectIdAndOwnerSurvive() { + RequireReiser4Progs(); + var writer = new Reiser4Writer(); + writer.AddFile("d/keep.bin", Payload(9_000, 6)); + var path = this.Save("rebuilt.img", writer.Build()); + var rebuild = FsInteropToolbox.RunWsl($"fsck.reiser4 --build-fs -y {FsInteropToolbox.WinToWsl(path)} l.EndsWith(" lost+found", StringComparison.Ordinal)); + var ownerBefore = Ownership(path, "/lost+found"); + + using var image = new MemoryStream(); + image.Write(File.ReadAllBytes(path)); + using (var reader = new Reiser4Reader(image)) { + var lost = reader.Entries.Single(static e => e.Name == "lost+found"); + Assert.Multiple(() => { + Assert.That(reader.NativeTreeValid, Is.True); + Assert.That(lost.IsDirectory, Is.True); + Assert.That(lostBefore, Does.Contain($":{lost.Metadata!.ObjectId:x}:"), "our reader and debugfs agree on the object id"); + Assert.That(reader.Extract(reader.Entries.Single(static e => e.Name == "d/keep.bin")), Is.EqualTo(Payload(9_000, 6))); + }); + } + new Reiser4FormatDescriptor().Add(image, [ArchiveInputInfo.InMemory("lost+found/found.bin", Payload(5_000, 7))]); + var edited = this.Save("rebuilt-added.img", image.ToArray()); + + AssertFsckConsistent(edited); + Assert.Multiple(() => { + Assert.That(Listing(edited, "/").Single(static l => l.EndsWith(" lost+found", StringComparison.Ordinal)), Is.EqualTo(lostBefore)); + Assert.That(Ownership(edited, "/lost+found"), Is.EqualTo(ownerBefore).And.Not.Empty, "fsck made lost+found with its own owner and times"); + Assert.That(ReadThroughProgs(edited, "lost+found/found.bin"), Is.EqualTo(Payload(5_000, 7))); + Assert.That(ReadThroughProgs(edited, "d/keep.bin"), Is.EqualTo(Payload(9_000, 6))); + }); + } + + [Test, Category("HappyPath")] + public void GivenALargeTimeStatExtension_WhenAddingAndDefragmenting_ThenFsckAcceptsItAndDebugfsStillShowsIt() { + RequireReiser4Progs(); + var plain = new Reiser4Writer(); + plain.AddFile("timed.bin", Payload(6_000, 8)); + Reiser4Reader.FileMetadata metadata; + using (var reader = new Reiser4Reader(new MemoryStream(plain.Build()))) + metadata = reader.Entries.Single().Metadata!; + // Mask bit 2 is the large-time extension: three 32-bit nanosecond fields after the Unix ones. + var raw = new byte[metadata.RawStatData.Length + 12]; + metadata.RawStatData.CopyTo(raw, 0); + BinaryPrimitives.WriteUInt16LittleEndian(raw, 0x7); + BinaryPrimitives.WriteUInt32LittleEndian(raw.AsSpan(44), 111_111_111); + BinaryPrimitives.WriteUInt32LittleEndian(raw.AsSpan(48), 222_222_222); + BinaryPrimitives.WriteUInt32LittleEndian(raw.AsSpan(52), 333_333_333); + var writer = new Reiser4Writer(); + writer.AddFile("timed.bin", Payload(6_000, 8), metadata with { RawStatData = raw }); + using var image = new MemoryStream(writer.Build()); + var descriptor = new Reiser4FormatDescriptor(); + descriptor.Add(image, [ArchiveInputInfo.InMemory("other.bin", Payload(7_000, 9))]); + descriptor.Defragment(image); + var path = this.Save("timed.img", image.ToArray()); + + AssertFsckConsistent(path); + var info = FsInteropToolbox.RunWsl($"debugfs.reiser4 -i /timed.bin {FsInteropToolbox.WinToWsl(path)} { + Assert.That(info.StdOut, Does.Contain("sdext_lt"), info.StdOut); + Assert.That(ReadThroughProgs(path, "timed.bin"), Is.EqualTo(Payload(6_000, 8))); + }); + using var result = new Reiser4Reader(image); + Assert.That(result.Entries.Single(static e => e.Name == "timed.bin").Metadata!.RawStatData, Is.EqualTo(raw)); + } +} diff --git a/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Reiser4/Reiser4FormatDescriptor.cs b/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Reiser4/Reiser4FormatDescriptor.cs index 246c121cf..79eb425eb 100644 --- a/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Reiser4/Reiser4FormatDescriptor.cs +++ b/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Reiser4/Reiser4FormatDescriptor.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +using System.Buffers.Binary; using System.Globalization; using System.Text; using Compression.Registry; @@ -7,11 +8,12 @@ namespace FileSystem.Reiser4; /// -/// Read-only descriptor for Reiser4 filesystem images (successor to ReiserFS 3.6 -/// — completely different on-disk layout). Surfaces the master superblock at -/// offset 65536 and, when present, the format40 superblock that follows it, -/// plus a structured metadata bundle and the raw image. Walking the twig-level -/// B-tree is explicitly out of scope (multi-week effort). +/// Descriptor for Reiser4 filesystem images (successor to ReiserFS 3.6 — completely +/// different on-disk layout). Volumes whose node40 tree the reader walks completely list +/// and extract their files and directories, and are created and edited through staged +/// rebuilds that carry the volume's identity and every object's metadata across; other +/// images surface the master superblock at offset 65536, the format40 superblock that +/// follows it, a structured metadata bundle and the raw image. /// /// Magic: /// @@ -253,6 +255,7 @@ private static void CreateCore(Stream output, IReadOnlyList in if (saved is not null) { w.Label = saved.Label; w.Uuid = Convert.FromHexString(saved.UuidHex); + w.MkfsId = saved.MkfsId; w.BlockCount = (ulong)(saved.Length / Reiser4Writer.BlockSize); w.AddDirectory("", saved.Root); } @@ -541,14 +544,31 @@ public Stream OpenEntry(Stream archive, string entryName, string? password) { public void Dispose() => this._reader.Dispose(); } - private sealed record RebuildMetadata(string Label, string UuidHex, long Length, + private sealed record RebuildMetadata(string Label, string UuidHex, uint MkfsId, long Length, Reiser4Reader.FileMetadata? Root, Dictionary Entries); + /// + /// Everything a rebuild has to carry across, taken before anything is written. A tree the + /// reader cannot walk completely, or fixed blocks the writer would not reproduce, are + /// refused here, so the source is never touched. + /// private static RebuildMetadata CaptureMetadata(Stream archive) { using var reader = new Reiser4Reader(archive); if (!reader.NativeTreeValid) throw new NotSupportedException("Reiser4: refusing to rebuild an incomplete or unsupported native tree."); - return new(reader.Label, reader.UuidHex, reader.Length, reader.RootMetadata, + var blocks = new Dictionary(); + byte[] Block(int number) { + if (blocks.TryGetValue(number, out var cached)) return cached; + var buffer = new byte[Reiser4Writer.BlockSize]; + archive.Position = (long)number * Reiser4Writer.BlockSize; + archive.ReadExactly(buffer); + return blocks[number] = buffer; + } + if (Reiser4Writer.ProfileMismatch(Block) is { } reason) + throw new NotSupportedException($"Reiser4: refusing to rebuild: {reason}."); + var mkfsId = BinaryPrimitives.ReadUInt32LittleEndian(Block(17).AsSpan(48, 4)); + archive.Position = 0; + return new(reader.Label, reader.UuidHex, mkfsId, reader.Length, reader.RootMetadata, reader.Entries.ToDictionary(static entry => entry.Name, StringComparer.Ordinal)); } diff --git a/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Reiser4/Reiser4Writer.cs b/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Reiser4/Reiser4Writer.cs index fb91f8ea8..0e5b8e197 100644 --- a/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Reiser4/Reiser4Writer.cs +++ b/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Reiser4/Reiser4Writer.cs @@ -10,9 +10,10 @@ namespace FileSystem.Reiser4; /// /// Creator for a Reiser4 filesystem image that starts from what /// mkfs.reiser4 -fffy from reiser4progs 1.2.2 produces and adds -/// regular files to the root directory through the native tree — stat data and -/// entries in the leaf, extents in the twig — so that fsck.reiser4 -/// validates the result as "FS is consistent." with or without files. +/// directories and regular files through the native tree — stat data and directory +/// entries in leaves, extents in twigs, as many internal levels above them as the +/// tree needs — so that fsck.reiser4 validates the result as +/// "FS is consistent." with or without files. /// /// /// The image holds 25 reserved blocks at fixed positions (block size = 4 KB): @@ -464,6 +465,50 @@ private static byte[] TrimLabel(string? label) { return bytes.Length <= 16 ? bytes : bytes[..16]; } + /// + /// Why the fixed blocks of an existing volume describe something this writer would not + /// write back, or when a rebuild reproduces them. + /// + /// + /// A rebuild writes the captured mkfs prefix again with only the volume's identity + /// patched in, so every other byte of those blocks has to be what the source already + /// holds. That is where a volume made with another profile shows: the format40 tail + /// policy, and the plugin set the backup record keeps of the root directory's — hash, + /// fibration, formatting. Writing the default profile over them leaves the root's own + /// plugin set disagreeing with its backup, which fsck.reiser4 reports, and names + /// keyed with a hash the volume does not use, which nothing can look up. + /// A status block other than "consistent" marks a volume the tools found damaged, + /// and a journal header or footer that is not blank belongs to a volume the kernel + /// wrote, whose transactions this reader does not replay. Both are left alone rather + /// than silently rewritten. + /// + internal static string? ProfileMismatch(Func readBlock) { + ArgumentNullException.ThrowIfNull(readBlock); + static bool SameOutside(ReadOnlySpan actual, ReadOnlySpan template, params (int Start, int End)[] volatileRanges) { + if (actual.Length != template.Length) return false; + for (var i = 0; i < template.Length; ++i) + if (actual[i] != template[i] && !volatileRanges.Any(range => i >= range.Start && i < range.End)) + return false; + return true; + } + + // Master: uuid, label and the legacy payload marker and pointer that follow them. + if (!SameOutside(readBlock(16), LoadTemplate(16), (MasterUuidOff, MasterPayloadDirOff + 8))) + return "the master superblock carries fields this writer does not reproduce"; + // Format40: the counters, the root, the mkfs id and the tree height move; magic, + // tail policy, flags and version are the profile. + if (!SameOutside(readBlock(17), LoadTemplate(17), (F40BlockCountOff, F40MagicOff), (F40TreeHeightOff, F40PolicyOff))) + return "the volume uses a tail policy or format40 flags other than the default profile"; + if (readBlock(19).Any(static b => b != 0) || readBlock(20).Any(static b => b != 0)) + return "the journal is not blank, and its transactions are not replayed"; + if (!SameOutside(readBlock(21), LoadTemplate(21))) + return "the status block does not mark the volume consistent"; + if (!SameOutside(readBlock(22), LoadTemplate(22), (BackupUuidOff, BackupLabelOff + 16), + (BackupF40BlockCountOff, BackupF40MkfsIdOff + 4))) + return "the root directory's plugin set (hash, fibration or formatting) is not the default profile"; + return null; + } + private static byte[] LoadTemplate(int blockNumber) { var resource = $"FileSystem.Reiser4.Templates.block_{blockNumber}.bin"; var asm = typeof(Reiser4Writer).Assembly; From b1bad7421883cc732d6845f654ec0ef37b907811 Mon Sep 17 00:00:00 2001 From: Hawkynt Date: Fri, 9 Oct 2026 11:37:55 +0200 Subject: [PATCH 3/7] # Reiser4 metadata updates refuse link-count and extension changes UpdateMetadata wrote whatever link count and stat-data bytes it was handed. Extension bytes the mask does not declare make the stat data a fatal corruption to fsck.reiser4 (stat40_check_struct), yet passed our own verification, which compares only what it wrote; a link count other than the namespace's is silently wrong (fsck --check does not flag a regular file's nlink, so nothing catches it). The link count follows the namespace (one per name, a directory one more per sub-directory) and the rebuild already recomputes it for directories; extension bytes past the Unix fields are carried as found. UpdateMetadata now changes only mode, UID, GID and the three timestamps, and refuses a different link count, mask or extension tail with NotSupportedException, the volume unchanged. Sourcing rung 2: stat40 layout as in docs/REISER4-NATIVE-PROFILE.md; reiser4progs used only as an oracle. --- .../Reiser4/Reiser4NativeTreeTests.cs | 26 +++++++++++++++++++ .../Reiser4FormatDescriptor.cs | 19 +++++++++++--- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/Compression.Tests/Reiser4/Reiser4NativeTreeTests.cs b/Compression.Tests/Reiser4/Reiser4NativeTreeTests.cs index be1a3bc02..52abd503b 100644 --- a/Compression.Tests/Reiser4/Reiser4NativeTreeTests.cs +++ b/Compression.Tests/Reiser4/Reiser4NativeTreeTests.cs @@ -451,6 +451,32 @@ public void VolumeOutsideTheWriterProfile_IsRefusedForEveryRebuild(int block, in }); } + [Test] + public void GivenALinkCountChange_WhenUpdatingMetadata_ThenItIsRefusedAndTheImageIsUntouched() { + var writer = new Reiser4Writer(); + writer.AddFile("f.bin", Payload(100, 34)); + var original = writer.Build(); + using var image = new MemoryStream(original.ToArray()); + Reiser4Reader.FileMetadata before; + using (var reader = new Reiser4Reader(image)) before = reader.Entries.Single().Metadata!; + Assert.Throws(() => + new Reiser4FormatDescriptor().UpdateMetadata(image, "f.bin", before with { LinkCount = 5 })); + Assert.That(image.ToArray(), Is.EqualTo(original)); + } + + [Test] + public void GivenNewExtensionBytes_WhenUpdatingMetadata_ThenTheyAreRefusedAndTheImageIsUntouched() { + var writer = new Reiser4Writer(); + writer.AddFile("f.bin", Payload(100, 35)); + var original = writer.Build(); + using var image = new MemoryStream(original.ToArray()); + Reiser4Reader.FileMetadata before; + using (var reader = new Reiser4Reader(image)) before = reader.Entries.Single().Metadata!; + var widened = before with { RawStatData = [.. before.RawStatData, 0xDE, 0xAD, 0xBE, 0xEF] }; + Assert.Throws(() => new Reiser4FormatDescriptor().UpdateMetadata(image, "f.bin", widened)); + Assert.That(image.ToArray(), Is.EqualTo(original)); + } + [Test, Category("Boundary")] public void GivenOneFile_WhenWriting_ThenTheTreeIsTheTwoLevelTreeMkfsWrites() { var writer = new Reiser4Writer(); diff --git a/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Reiser4/Reiser4FormatDescriptor.cs b/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Reiser4/Reiser4FormatDescriptor.cs index 79eb425eb..2f0b0d9d1 100644 --- a/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Reiser4/Reiser4FormatDescriptor.cs +++ b/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Reiser4/Reiser4FormatDescriptor.cs @@ -613,10 +613,13 @@ private static bool MetadataMatches(Reiser4Reader.FileMetadata expected, Reiser4 expected.RawStatData.AsSpan(0, 2).SequenceEqual(actual.RawStatData.AsSpan(0, 2)) && expected.RawStatData.AsSpan(44).SequenceEqual(actual.RawStatData.AsSpan(44)); - /// Updates a native object's Unix fields and stat-data extensions through a staged rebuild. + /// Updates a native object's mode, owner and timestamps through a staged rebuild. /// The readable, writable, seekable volume to update. /// The member path, or an empty string for the root directory. - /// Unix fields and stat-data extension bytes to retain. + /// The object's metadata with new mode, UID, GID or timestamps; its link + /// count and stat-data extension bytes must be the ones the volume holds. + /// The update changes the object's kind, its link count or + /// its extension bytes, or the volume is outside the supported profile; the volume is unchanged. public void UpdateMetadata(Stream archive, string name, Reiser4Reader.FileMetadata metadata) { ArgumentNullException.ThrowIfNull(metadata); var saved = CaptureMetadata(archive); @@ -625,9 +628,19 @@ public void UpdateMetadata(Stream archive, string name, Reiser4Reader.FileMetada if (original is null) throw new FileNotFoundException("Reiser4: native object was not found.", name); if ((metadata.Mode & 0xF000) != (original.Mode & 0xF000)) throw new NotSupportedException("Reiser4: metadata updates cannot change an object's kind."); + // The link count follows the namespace: a file has one name, a directory one more than + // its sub-directories. Any other value is what fsck.reiser4 reports as wrong. + if (metadata.LinkCount != original.LinkCount) + throw new NotSupportedException("Reiser4: the link count follows the namespace and cannot be set."); + // Extension bytes past the Unix fields are carried as found because their meaning is not + // verified here; accepting new ones would write a stat data nothing has checked. + if (metadata.RawStatData.Length != original.RawStatData.Length || + !metadata.RawStatData.AsSpan(0, 2).SequenceEqual(original.RawStatData.AsSpan(0, 2)) || + !metadata.RawStatData.AsSpan(Math.Min(44, metadata.RawStatData.Length)).SequenceEqual(original.RawStatData.AsSpan(Math.Min(44, original.RawStatData.Length)))) + throw new NotSupportedException("Reiser4: stat-data extensions other than the Unix fields cannot be changed."); var updated = metadata with { Size = original.Size, ObjectId = original.ObjectId, StatLocality = original.StatLocality, - RawStatData = metadata.RawStatData.ToArray(), + RawStatData = original.RawStatData.ToArray(), }; if (normalized.Length == 0) saved = saved with { Root = updated }; else saved.Entries[normalized] = saved.Entries[normalized] with { Metadata = updated }; From b235125a7a7aad65ec408f5b9bf8045fabc27181 Mon Sep 17 00:00:00 2001 From: Hawkynt Date: Fri, 9 Oct 2026 11:36:40 +0200 Subject: [PATCH 4/7] # Reiser4 writes only stat data whose extensions parse The writer carried any stat-data bytes a rebuild or caller handed it. A body with four bytes its mask does not declare - the shape the round-trip test used to prove "opaque extension retention" - is, to fsck.reiser4, a fatal corruption (stat40_check_struct, "Fatal corruptions were found"), while our reader, which reads only the first 44 bytes, accepted it back. Extensions follow the 16-bit mask in bit order, each with a length fixed by its plugin or by a leading slot count. The writer now walks the mask before using a carried body: light-weight, Unix, large times, flags, plugin set and heir set must exactly fill the body; any other extension (symlink, crypto, a chained mask) or a leftover byte is NotSupportedException. A rebuild of a volume carrying such a stat data is therefore refused before its source is touched. The round-trip test now carries a real large-time extension instead of junk; new tests refuse trailing bytes, the symlink, crypto and chained-mask bits, and a volume whose stat data declares a large-time extension it does not hold (image byte-identical). Sourcing rung 2: extension ids and sizes (sdext_lw/unix/lt/flags/plug) are constants read from reiser4progs 1.2.2 headers; fsck.reiser4 was the oracle for the corruption verdict. --- .../Reiser4/Reiser4NativeTreeTests.cs | 53 ++++++++++++++++++- .../FileSystem.Reiser4/Reiser4Tree.cs | 39 ++++++++++++++ 2 files changed, 91 insertions(+), 1 deletion(-) diff --git a/Compression.Tests/Reiser4/Reiser4NativeTreeTests.cs b/Compression.Tests/Reiser4/Reiser4NativeTreeTests.cs index 52abd503b..957765405 100644 --- a/Compression.Tests/Reiser4/Reiser4NativeTreeTests.cs +++ b/Compression.Tests/Reiser4/Reiser4NativeTreeTests.cs @@ -250,7 +250,9 @@ public void Rebuild_RetainsStatExtensionsEmptyDirectoriesAndVolumeIdentity() { using var source = new MemoryStream(writer.Build()); using var initial = new Reiser4Reader(source); var metadata = initial.Entries.Single(static e => !e.IsDirectory).Metadata!; - var raw = metadata.RawStatData.Concat(new byte[] { 0xDE, 0xAD, 0xBE, 0xEF }).ToArray(); + // A large-time extension (mask bit 2): three nanosecond fields after the Unix ones. + var raw = metadata.RawStatData.Concat(new byte[] { 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0 }).ToArray(); + BinaryPrimitives.WriteUInt16LittleEndian(raw, 0x7); BinaryPrimitives.WriteUInt32LittleEndian(raw.AsSpan(16), 1234); BinaryPrimitives.WriteUInt32LittleEndian(raw.AsSpan(20), 5678); var retained = metadata with { UserId = 1234, GroupId = 5678, RawStatData = raw }; @@ -451,6 +453,55 @@ public void VolumeOutsideTheWriterProfile_IsRefusedForEveryRebuild(int block, in }); } + private static Reiser4Reader.FileMetadata MetadataOfOneFile() { + var writer = new Reiser4Writer(); + writer.AddFile("f.bin", Payload(100, 39)); + using var reader = new Reiser4Reader(new MemoryStream(writer.Build())); + return reader.Entries.Single().Metadata!; + } + + [Test] + public void GivenStatDataBytesItsMaskDoesNotDeclare_WhenWriting_ThenTheyAreRefused() { + var metadata = MetadataOfOneFile(); + var writer = new Reiser4Writer(); + writer.AddFile("f.bin", Payload(100, 39), metadata with { RawStatData = [.. metadata.RawStatData, 0xDE, 0xAD, 0xBE, 0xEF] }); + Assert.Throws(() => writer.Build()); + } + + [TestCase(3, TestName = "GivenASymlinkStatExtension_WhenWriting_ThenItIsRefused")] + [TestCase(7, TestName = "GivenACryptoStatExtension_WhenWriting_ThenItIsRefused")] + [TestCase(15, TestName = "GivenAChainedExtensionMask_WhenWriting_ThenItIsRefused")] + public void UnsupportedStatExtension_IsRefused(int bit) { + var metadata = MetadataOfOneFile(); + var raw = metadata.RawStatData.ToArray(); + BinaryPrimitives.WriteUInt16LittleEndian(raw, (ushort)(0x3 | 1 << bit)); + var writer = new Reiser4Writer(); + writer.AddFile("f.bin", Payload(100, 39), metadata with { RawStatData = raw }); + Assert.Throws(() => writer.Build()); + } + + [Test] + public void GivenAVolumeCarryingAStatExtensionThatDoesNotParse_WhenAdding_ThenTheEditIsRefusedAndTheImageIsUntouched() { + var writer = new Reiser4Writer(); + writer.AddFile("f.bin", Payload(100, 40)); + var original = writer.Build(); + // Declare a large-time extension the 44-byte body does not have room for. + using (var reader = new Reiser4Reader(new MemoryStream(original))) { + var at = FindStatData(original, reader.Entries.Single().Metadata!.RawStatData); + BinaryPrimitives.WriteUInt16LittleEndian(original.AsSpan(at), 0x7); + } + using var image = new MemoryStream(original.ToArray()); + Assert.Throws(() => new Reiser4FormatDescriptor().Add(image, + [ArchiveInputInfo.InMemory("x.bin", Payload(10, 41))])); + Assert.That(image.ToArray(), Is.EqualTo(original)); + } + + private static int FindStatData(byte[] image, byte[] statData) { + for (var i = 0; i <= image.Length - statData.Length; ++i) + if (image.AsSpan(i, statData.Length).SequenceEqual(statData)) return i; + throw new AssertionException("stat data not found in the image"); + } + [Test] public void GivenALinkCountChange_WhenUpdatingMetadata_ThenItIsRefusedAndTheImageIsUntouched() { var writer = new Reiser4Writer(); diff --git a/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Reiser4/Reiser4Tree.cs b/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Reiser4/Reiser4Tree.cs index 533dd6ea6..3c68a540b 100644 --- a/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Reiser4/Reiser4Tree.cs +++ b/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Reiser4/Reiser4Tree.cs @@ -142,9 +142,48 @@ private static (ulong Ordering, ulong ObjectId, ulong Offset, bool Hashed) NameK return (ordering | (ulong)Fibre(name) << 57, objectId, offset, hashed); } + /// + /// Why a stat-data body does not hold exactly the extensions its mask declares, or + /// when it does. + /// + /// + /// Extensions follow the 16-bit mask in bit order: light-weight (bit 0, 14 bytes), + /// Unix (bit 1, 28), large times (bit 2, three 32-bit nanosecond fields), the plugin + /// set (bit 4) and heir set (bit 8) — a 16-bit count of 4-byte member/plugin slots — + /// and flags (bit 5, 32 bits). Anything else, including bit 15, which chains a + /// further mask, is not carried: a body that does not parse is what + /// fsck.reiser4 calls a fatal corruption. + /// + internal static string? StatDataProblem(ReadOnlySpan body) { + if (body.Length < 2) return "the stat data has no extension mask"; + var mask = BinaryPrimitives.ReadUInt16LittleEndian(body); + if ((mask & 3) != 3) return "the stat data lacks the light-weight or Unix extension"; + var at = 2; + for (var bit = 0; bit < 16; ++bit) { + if ((mask & 1 << bit) == 0) continue; + int length; + switch (bit) { + case 0: length = 14; break; + case 1: length = 28; break; + case 2: length = 12; break; + case 5: length = 4; break; + case 4 or 8: + if (at + 2 > body.Length) return "a plugin-set extension is truncated"; + length = 2 + 4 * BinaryPrimitives.ReadUInt16LittleEndian(body[at..]); + break; + default: return $"stat-data extension {bit} is not supported"; + } + if (at + length > body.Length) return $"stat-data extension {bit} is truncated"; + at += length; + } + return at == body.Length ? null : "the stat data holds bytes its extension mask does not declare"; + } + /// A stat data saying what a file is and how long. private static byte[] StatData(ushort mode, uint links, ulong size, ulong bytes, uint time, Reiser4Reader.FileMetadata? metadata = null) { + if (metadata is { RawStatData.Length: > 0 } carried && StatDataProblem(carried.RawStatData) is { } problem) + throw new NotSupportedException($"Reiser4: {problem}."); if (metadata is { } saved && saved.RawStatData.Length >= 44) { var preserved = saved.RawStatData.ToArray(); BinaryPrimitives.WriteUInt16LittleEndian(preserved.AsSpan(2), saved.Mode); From fc185b6f22f8191b9901c923044ce88f64988dfb Mon Sep 17 00:00:00 2001 From: Hawkynt Date: Fri, 9 Oct 2026 11:38:19 +0200 Subject: [PATCH 5/7] * Reiser4 docs record the multi-level profile, its refusals and the reiser4progs evidence The package README row still described the root-only single-leaf profile, the bullet claimed "opaque stat-data extensions" survive rebuilds (a body with undeclared bytes is a fatal corruption to fsck.reiser4) and linked the profile notes relatively, which the package-readme generator rejects because nuget.org renders package READMEs. The README row and bullet and docs/REISER4-NATIVE-PROFILE.md now say what was verified: trees up to height 4, nested and empty directories, identity (UUID, label, mkfs id, object IDs, declared stat-data extensions) kept through edits, defrag in place only for a two-node tree, refusal of other plugin profiles, non-blank journals, unclean status and unparsable stat data, and no kernel mount (the WSL kernel has no Reiser4 driver). The profile notes carry the oracle table. REFERENCE.md regenerated. --- Hawkynt.FileFormats.FileSystems/README.md | 6 +- Hawkynt.FileFormats.FileSystems/REFERENCE.md | 50 ++++++++-- docs/REISER4-NATIVE-PROFILE.md | 96 ++++++++++++++------ 3 files changed, 112 insertions(+), 40 deletions(-) diff --git a/Hawkynt.FileFormats.FileSystems/README.md b/Hawkynt.FileFormats.FileSystems/README.md index 92c53f241..a2a7a56a8 100644 --- a/Hawkynt.FileFormats.FileSystems/README.md +++ b/Hawkynt.FileFormats.FileSystems/README.md @@ -128,7 +128,7 @@ The `Id`, `State` and verb columns are read off the descriptors by `FilesystemRe | [OCFS2 (Oracle Cluster Filesystem 2)](https://en.wikipedia.org/wiki/OCFS2) | `Ocfs2` | R/W | ✅ | ✅ moving | — | ✅ | ✅ | — | ✅ | Linux kernel `ocfs2` driver (libguestfs appliance) mounts the written volume and reads the tree back byte-identical with modes, owners and times; [`fsck.ocfs2 -fn`](https://github.com/markfasheh/ocfs2-tools) clean before and after a kernel read-write mount, and after in-place add / replace / remove (nested too) / defrag / shrink on our volumes and on `mkfs.ocfs2 -M local` volumes filled by the kernel; kernel-written files with extent trees of depth 1 and 2 extract with the kernel's SHA-256 | Single-node (local) volumes; in-place edits cover files in inline directories (missing directories are made), extent-backed directories are refused; shrink trims the free tail in place; journal at least the kernel's 4 MiB minimum | [OCFS2](https://www.kernel.org/doc/html/latest/filesystems/ocfs2.html) | | [QNX4 FS](https://en.wikipedia.org/wiki/QNX4FS) | `Qnx4` | R/W | ✅ | ✅ moving | — | ✅ | ✅ | — | ✅ | own reader + struct-parity tests | — | [qnx4](https://www.kernel.org/doc/html/latest/filesystems/qnx4.html) | | [QNX6 Neutrino FS](https://en.wikipedia.org/wiki/QNX6FS) | `Qnx6` | R/W | ✅ | ✅ moving | — | ✅ | ✅ | — | ✅ | own reader + struct-parity tests | — | [qnx6](https://www.kernel.org/doc/html/latest/filesystems/qnx6.html) | -| [Reiser4](https://en.wikipedia.org/wiki/Reiser4) | `Reiser4` | R/W | ✅ | ✅ moving | — | ✅ | ✅ | — | ✅ | `fsck.reiser4` + `debugfs.reiser4` (WSL-gated) after create, streamed create, add/replace/remove and defrag | Root-level regular files in one leaf under one twig, extents in the twig; edits rebuild the volume | [Reiser4](https://reiser4.wiki.kernel.org/) | +| [Reiser4](https://en.wikipedia.org/wiki/Reiser4) | `Reiser4` | R/W | ✅ | ✅ moving | — | ✅ | ✅ | — | ✅ | `fsck.reiser4 --check` + `debugfs.reiser4` (WSL-gated): trees up to height 4, nested/empty directories, bytes read through the tools' own lookup; edits on `mkfs.reiser4` volumes and on trees `fsck.reiser4 --build-fs` rewrote | Multi-level node40 trees with nested and empty directories; edits rebuild the volume keeping UUID, label, mkfs id, object IDs and stat data; volumes of another plugin profile (hash, fibration, tail policy) are read but not rewritten; defrag moves in place only for a two-node tree | [Reiser4](https://reiser4.wiki.kernel.org/) | | [ReiserFS](https://en.wikipedia.org/wiki/ReiserFS) | `ReiserFs` | R/W | ✅ | ✅ moving | — | ✅ | ✅ | — | ✅ | [`reiserfsck`](https://git.kernel.org/pub/scm/linux/kernel/git/jeffm/reiserfsprogs.git), also after mutation | ReiserFS 3.6; edits rebuild the tree | [ReiserFS documentation](https://www.kernel.org/doc/html/latest/filesystems/reiserfs.html) | | [TUX2](https://en.wikipedia.org/wiki/Tux2) | `Tux2` | R/W | ✅ | ✅ moving | — | ✅ | ✅ | — | ✅ | own reader + struct-parity tests | Phase-tree filesystem | [Tux2](https://en.wikipedia.org/wiki/Tux2) | | [TUX3](https://en.wikipedia.org/wiki/Tux3) | `Tux3` | R | ✅ | — | — | ✅ | ✅ | — | — | own reader + struct-parity tests | — | [Tux3](https://github.com/OGAWAHirofumi/tux3) | @@ -282,7 +282,7 @@ ops.Remove(image, ["var/log/old.log"]); -Every public and protected member of all 871 types, generated from the built assembly and its XML documentation, is in [REFERENCE.md](https://github.com/Hawkynt/CompressionWorkbench/blob/main/Hawkynt.FileFormats.FileSystems/REFERENCE.md). +Every public and protected member of all 872 types, generated from the built assembly and its XML documentation, is in [REFERENCE.md](https://github.com/Hawkynt/CompressionWorkbench/blob/main/Hawkynt.FileFormats.FileSystems/REFERENCE.md). @@ -333,7 +333,7 @@ cwb recover raw.img --mode files --format Jpeg,Png - Where **Notes** says a format's edits *rebuild* the volume, an add or remove costs the whole image rather than the bytes that changed, and the block placement of untouched files may move. - **bcachefs** creation, in-place add/replace, reading and extraction preserve inode timestamps (to 100 ns, before the epoch and past 2038), UID/GID and permission bits when supplied through the archive-input metadata; ordinary file-backed inputs capture available host timestamps and mode automatically. Applying UID/GID on extraction depends on host APIs and is not available through the portable .NET filesystem API. Volumes are written at metadata version 1.3, the newest the distributions' bcachefs-tools open, so their `fsck` can judge them; newer tools upgrade such a volume as they open it. Mutation is profile-gated: the in-place writer owns single-device regular pointer extents as emitted by this package, and re-publishes every tree from what it read, so a volume another implementation wrote (packed keys, appended bsets, a journal with keys in it, another metadata version) is refused for mutation with the reason and left untouched; it is still read, including packed and multi-bset nodes, sparse files and symbolic links. Compressed and encrypted extents are recognised and refused per file, not decoded. Bucket generations are carried through a mutation: emptying a bucket advances its generation, and the alloc key, the `bucket_gens` slot, the extent pointer and its backpointer are written from one source and checked against each other before the commit is published. Foreign volumes with extra live b-trees, non-canonical `bucket_gens` or `alloc_v4` keys, inline / reflink / compressed extent keys, or unsupported inode and dirent types are refused for mutation rather than rewritten speculatively; read support is broader. What a volume carries and how each value was established is written up in [BCACHEFS-ON-DISK.md](https://github.com/Hawkynt/CompressionWorkbench/blob/main/docs/BCACHEFS-ON-DISK.md). - **ReFS** edits are offline-quiescent: the image must not be mounted, and `RefsMutationMode.NativeCow` stays fail-closed. The reader walks ReFS 3.x containers, MSB+ trees, allocators, Block Refcount rows and MLog framing; the offline editor relocates data and metadata pages, replaces and removes regular files and empty directories through CoW B+ replacement and alternate-checkpoint publication. Block Refcount row creation and whole-file clone semantics are implemented and tested: `RefsOfflineBlockCloner.CloneWholeFile` repoints an existing destination stream at an existing, equal-sized, cluster-aligned source stream, materialises the shared-cluster reference counts — including the first clone of a slot the sparse table does not yet track — and publishes Object Table root #0 and Block Refcount root #6 through one alternate checkpoint, reclaiming the destination's former allocation only once that checkpoint is durable. Writing to a clone afterwards reallocates rather than overwriting the shared clusters. **No public verb reaches this primitive**: it is a library entry point, not something `cwb` can be asked to do, and the profile is deliberately narrower than `FSCTL_DUPLICATE_EXTENTS_TO_FILE` — resident, sparse, integrity, zero-length, already-shared and partial-range clones are all refused. Still open before a mounted driver could use the same core: redo-payload codecs and replay for every opcode, every Schema Table key rule, the allocation-zone policies of the three allocators, container create / delete / move, partial-range and EOF-extending clones, hard links, sparse and integrity-stream mutation beyond 4 KiB clusters, snapshots, ADS, security descriptors, reparse points, USN, rename, truncate, a format path, locking and a Windows `chkdsk` corpus. -- **Reiser4** R/W supports native node40 trees with multiple leaves and internal levels, nested and empty directories, and twig extent items. Rebuild edits preserve object IDs, mode, UID/GID, timestamps, volume identity and opaque stat-data extensions; directory accounting follows namespace changes. `UpdateMetadata` applies Unix fields through a staged rebuild. A 5,000-file tree with three levels and nested directories passes `fsck.reiser4`, including metadata updates, add/remove and defrag. The existing two-node flat profile retains in-place defrag in both directions; larger trees use staged consolidation at the start. Separate xattr items, symlinks, sparse/shared files, non-ASCII names and alternate node/item plugins remain outside this profile. Unsupported trees are refused before rebuild, and older private payload-chain images remain readable. See [native profile notes](../docs/REISER4-NATIVE-PROFILE.md). +- **Reiser4** R/W writes and reads node40 trees with as many leaves, twigs and internal levels as the namespace needs, nested and empty directories, and extents in twig nodes. Edits, defrag, shrink and relayout are staged rebuilds that keep the volume's UUID, label and mkfs id, every object ID, mode, owner, timestamps and declared stat-data extensions (large times, flags, plugin and heir sets); `UpdateMetadata` sets mode, owner and timestamps the same way and refuses link-count or extension changes. `fsck.reiser4` calls trees of height 3 and 4 consistent before and after those edits, and file bytes agree with where `debugfs.reiser4` finds them. A rebuild is refused, the image untouched, when the volume uses another plugin profile (a hash, fibration or tail policy other than mkfs's default), has a non-blank journal or a status other than consistent, or holds items outside the profile — tails, ctails, xattr items, symlinks, other stat-data extensions, sparse or shared files, non-ASCII names, short keys. No Reiser4-capable kernel was reachable, so nothing was mounted. Older private payload-chain images remain readable. See [native profile notes](https://github.com/Hawkynt/CompressionWorkbench/blob/main/docs/REISER4-NATIVE-PROFILE.md). - **GFS2** R/W is the standalone `lock_nolock` profile with regular files in the root directory; small files are stuffed and larger files use the indirect tree. Existing-image edits rebuild the volume while preserving its size floor, lock-table value, UUID and each regular file's mode, owner, timestamps (with nanoseconds) and access-policy flags; layout-changing flags such as JDATA are not carried over because the rebuilt file is laid out plainly. Directory attributes are not preserved. ExHash/nested-directory writing, journal replay, cluster-lock-manager state and extended attributes remain out of scope. - **NSS** native read support is intentionally narrow and quiescent: the reader reconstructs validated DirH/LEAF namespace relationships and ordinary contiguous file extents using the OES-derived clean-room profile in [NSS-ON-DISK.md](https://github.com/Hawkynt/CompressionWorkbench/blob/main/docs/NSS-ON-DISK.md), while retaining anchor diagnostics for unsupported images. Native allocation trees, journals, trustee metadata, ZLBH history, compressed/sparse/fragmented data profiles and writable NSS pools remain out of scope; the existing writer still emits a private Workbench container rather than a pool NetWare/OES would mount. - **HFS+** is listed as a POSIX system shows it: a catalog '/' appears as ':' (and a ':' given to the writer is stored as '/'), file hard links carry their indirect node's data under every name, and the metadata directories and journal files are left out. Directory hard links are not resolved, so a non-empty `.HFS+ Private Directory Data` folder stays visible. Every decmpfs (HFS+ transparent compression) method is read: 1, 3/4 zlib, 5 (zeros, as libfshfs reads it), 7/8 LZVN, 9/10 raw, 11/12 LZFSE and 13/14 LZBITMAP; a dataless placeholder (0x80000001/0x80000002) has no local content and lists and extracts as empty, with `Kind` `dataless`. The writer's `Compression` option (default `None`) stores files compressed with any of those methods except 5 and the dataless types, which do not encode given content: inline in the attribute when the encoding fits (3802 bytes, files up to 64 KiB), otherwise as 64 KiB resource-fork chunks; a file that would not shrink stays uncompressed. libfshfs reads the zlib, LZVN, LZFSE and raw volumes byte for byte; it implements neither method 1 nor LZBITMAP, so those two have no independent HFS+ reader here (the LZBITMAP stream itself is checked against dissect.util). In-place add writes uncompressed files only. Removing a file in place frees every extent of both forks; removing or replacing a hard link, a file with extended attributes or transparent compression, or a fork continued in the extents overflow file is refused, as is a removal that would change the first key of a catalog leaf or empty it (the B-tree is not rebalanced in place). On extraction, characters the host cannot hold in a file name (on Windows `: < > " | ? *`, control characters, trailing dots and blanks) become `_`, as for every format. diff --git a/Hawkynt.FileFormats.FileSystems/REFERENCE.md b/Hawkynt.FileFormats.FileSystems/REFERENCE.md index 095679417..7b7aa8fdd 100644 --- a/Hawkynt.FileFormats.FileSystems/REFERENCE.md +++ b/Hawkynt.FileFormats.FileSystems/REFERENCE.md @@ -12109,7 +12109,7 @@ Performs the bounded offline ReFS block-clone operation for two existing, equal- ### Namespace `FileSystem.Reiser4` -[`Reiser4BlockMover`](#reiser4blockmover) · [`Reiser4FormatDescriptor`](#reiser4formatdescriptor) · [`Reiser4MasterSb`](#reiser4mastersb) · [`Reiser4Reader`](#reiser4reader) · [`Reiser4Reader.Entry`](#reiser4readerentry) · [`Reiser4Writer`](#reiser4writer) +[`Reiser4BlockMover`](#reiser4blockmover) · [`Reiser4FormatDescriptor`](#reiser4formatdescriptor) · [`Reiser4MasterSb`](#reiser4mastersb) · [`Reiser4Reader`](#reiser4reader) · [`Reiser4Reader.Entry`](#reiser4readerentry) · [`Reiser4Reader.FileMetadata`](#reiser4readerfilemetadata) · [`Reiser4Writer`](#reiser4writer) #### `Reiser4BlockMover` @@ -12131,7 +12131,7 @@ Implements `IFilesystemBlockMover`. #### `Reiser4FormatDescriptor` -Read-only descriptor for Reiser4 filesystem images (successor to ReiserFS 3.6 — completely different on-disk layout). Surfaces the master superblock at offset 65536 and, when present, the format40 superblock that follows it, plus a structured metadata bundle and the raw image. Walking the twig-level B-tree is explicitly out of scope (multi-week effort). Magic: `"ReIsEr4"` at offset 65536 — master superblock `ms_magic[16]`. References: `https://archive.kernel.org/oldwiki/reiser4.wiki.kernel.org/` — archived Reiser4 wiki (format40 layout, plugin system)reiser4progs (`mkfs.reiser4` / `debugfs.reiser4`) — canonical userspace tooling`https://en.wikipedia.org/wiki/Reiser4` — Wikipedia article +Descriptor for Reiser4 filesystem images (successor to ReiserFS 3.6 — completely different on-disk layout). Volumes whose node40 tree the reader walks completely list and extract their files and directories, and are created and edited through staged rebuilds that carry the volume's identity and every object's metadata across; other images surface the master superblock at offset 65536, the format40 superblock that follows it, a structured metadata bundle and the raw image. Magic: `"ReIsEr4"` at offset 65536 — master superblock `ms_magic[16]`. References: `https://archive.kernel.org/oldwiki/reiser4.wiki.kernel.org/` — archived Reiser4 wiki (format40 layout, plugin system)reiser4progs (`mkfs.reiser4` / `debugfs.reiser4`) — canonical userspace tooling`https://en.wikipedia.org/wiki/Reiser4` — Wikipedia article Implements `IArchiveCreatable`, `IArchiveDefragmentable`, `IArchiveFormatOperations`, `IArchiveModifiable`, `IArchivePurgeable`, `IArchiveShrinkable`, `IArchiveWriteConstraints`, `IFilesystemExtentMap`, `IFormatDescriptor`, `IFormatOptionsSchema`, `ILayoutOptimizable`, `IWipeEmpty`. @@ -12154,14 +12154,19 @@ Implements `IArchiveCreatable`, `IArchiveDefragmentable`, `IArchiveFormatOperati | `MinTotalArchiveSize` | `long? MinTotalArchiveSize { get; }` | Gets the min total archive size. | | `OptionsSchema` | `IReadOnlyList OptionsSchema { get; }` | Knobs the empty-filesystem writer actually honours. `VolumeLabel` is written into the master superblock label field (and the backup record) and surfaces through `fsck.reiser4` / our metadata readback; `ImageSize` drives `BlockCount` (4 KB blocks, clamped to the writer minimum). The 4 KB block size is fixed — the embedded mkfs.reiser4 templates are byte-exact 4096-byte captures — so it is intentionally not exposed. | | `TarCompressionFormatId` | `string TarCompressionFormatId { get; }` | Gets the tar compression format id. | +| `Add` | `void Add(Stream archive, IReadOnlyList inputs)` | Adds or replaces members while retaining existing stat-data extensions. | | `CanAccept` | `bool CanAccept(ArchiveInputInfo input, out string reason)` | Performs the can accept operation. | -| `CreateFromStreams` | `void CreateFromStreams(Stream target, IEnumerable inputs, FormatCreateOptions options)` | Creates the supported native single-leaf profile from input streams. Each source is consumed once into a temporary file, then copied in bounded chunks by `Reiser4Writer`; no complete file is staged in memory. Directory inputs are currently ignored because this writer emits root-level regular files only. | +| `CreateFromStreams` | `void CreateFromStreams(Stream target, IEnumerable inputs, FormatCreateOptions options)` | Creates the supported native node40 profile from input streams. Each source is consumed once into a temporary file, then copied in bounded chunks by `Reiser4Writer`; no complete file is staged in memory. Directory inputs retain empty directories and nested paths. | | `Create` | `void Create(Stream output, IReadOnlyList inputs, FormatCreateOptions options)` | Performs the create operation. | | `Defragment` | `void Defragment(Stream archive)` | Performs the defragment operation. | | `Defragment` | `void Defragment(Stream archive, DefragOptions options)` | Rewrites the volume with every file laid out contiguously from the start of the payload area. Each entry is spilled to scratch and the writer pulls it back, so the rebuild is not bounded by what a byte[] can hold. | | `EnumerateExtents` | `IEnumerable EnumerateExtents(Stream image)` | Reports the volume's layout: the reserved blocks and the payload directory chain as metadata, then each file's blocks. A file's blocks are consecutive apart from the block-allocator bitmaps they step over. | | `Extract` | `void Extract(Stream stream, string outputDir, string password, string[] files)` | Decodes the supplied input. | | `List` | `List List(Stream stream, string password)` | Lists the entries in the supplied container. | +| `RebuildStreaming` | `void RebuildStreaming(Stream source, Stream target, LayoutRebuildOptions options)` | Rebuilds the supported native profile while carrying existing metadata. | +| `Remove` | `void Remove(Stream archive, string[] entryNames)` | Removes members and directory subtrees through a metadata-preserving rebuild. | +| `Shrink` | `void Shrink(Stream input, Stream output)` | Rebuilds tightly while retaining native metadata, or copies through on failure. | +| `UpdateMetadata` | `void UpdateMetadata(Stream archive, string name, FileMetadata metadata)` | Updates a native object's mode, owner and timestamps through a staged rebuild. | | `WipeUnusedSpace` | `long WipeUnusedSpace(Stream image, bool wipeClusterTips = true, bool wipeDeletedEntries = true)` | Zeros every byte no live file and no metadata block occupies. | #### `Reiser4MasterSb` @@ -12206,9 +12211,11 @@ Implements `IDisposable`. | `Reiser4Reader` | `Reiser4Reader(Stream stream, bool leaveOpen = true)` | Initializes a new instance of `Reiser4Reader`. | | `MasterOffset` | `const long MasterOffset` | Byte offset of the master superblock: block 16 at a 4 KB block size. | | `BlockSize` | `int BlockSize { get; }` | Filesystem block size from the master superblock. | -| `Entries` | `IReadOnlyList Entries { get; }` | Files found in the native tree, or in the legacy payload directory. | +| `Entries` | `IReadOnlyList Entries { get; }` | Files and directories found in the native tree, or files in the legacy payload directory. | | `Label` | `string Label { get; }` | Volume label from the master superblock. | | `Length` | `long Length { get; }` | Total size of the backing image in bytes. | +| `NativeTreeValid` | `bool NativeTreeValid { get; }` | True when the complete supported native namespace was decoded. | +| `RootMetadata` | `FileMetadata RootMetadata { get; }` | Root stat-data, including inherited plugin settings. | | `UuidHex` | `string UuidHex { get; }` | Volume UUID from the master superblock, as hex. | | `Valid` | `bool Valid { get; }` | True when the image carries a valid Reiser4 master superblock. | | `Dispose` | `void Dispose()` | Releases resources held by this instance. | @@ -12218,20 +12225,44 @@ Implements `IDisposable`. #### `Reiser4Reader.Entry` -One regular file: its name, first data block and byte length. +One filesystem object and its physical extents. Implements `IEquatable`. | Member | Signature | Summary | | --- | --- | --- | -| `Entry` | `Entry(string Name, ulong FirstBlock, long Size)` | One regular file: its name, first data block and byte length. | +| `Entry` | `Entry(string Name, ulong FirstBlock, long Size)` | One filesystem object and its physical extents. | | `FirstBlock` | `ulong FirstBlock { get; init; }` | | +| `IsDirectory` | `bool IsDirectory { get; init; }` | True when this entry is a directory. | +| `Metadata` | `FileMetadata Metadata { get; init; }` | On-disk stat-data fields retained for metadata-preserving rebuilds. | | `Name` | `string Name { get; init; }` | | | `Size` | `long Size { get; init; }` | | +#### `Reiser4Reader.FileMetadata` + +Stat-data fields which can be represented by the current writer. + +Implements `IEquatable`. + +| Member | Signature | Summary | +| --- | --- | --- | +| `FileMetadata` | `FileMetadata(long Size, ushort Mode, uint LinkCount, uint UserId, uint GroupId, uint ModifiedTime, uint AccessedTime, uint ChangedTime, byte[] RawStatData)` | Stat-data fields which can be represented by the current writer. | +| `AccessedTime` | `uint AccessedTime { get; init; }` | | +| `ChangedTime` | `uint ChangedTime { get; init; }` | | +| `GroupId` | `uint GroupId { get; init; }` | | +| `LastModified` | `DateTime? LastModified { get; }` | Last-write timestamp, when the seconds value is representable. | +| `LinkCount` | `uint LinkCount { get; init; }` | | +| `Mode` | `ushort Mode { get; init; }` | | +| `ModifiedTime` | `uint ModifiedTime { get; init; }` | | +| `ObjectId` | `ulong ObjectId { get; init; }` | Stable filesystem object identity. | +| `RawStatData` | `byte[] RawStatData { get; init; }` | | +| `Size` | `long Size { get; init; }` | | +| `StatLocality` | `ulong StatLocality { get; init; }` | Locality of the object stat key. | +| `UserId` | `uint UserId { get; init; }` | | + #### `Reiser4Writer` -Creator for a Reiser4 filesystem image that starts from what `mkfs.reiser4 -fffy` from `reiser4progs 1.2.2` produces and adds regular files to the root directory through the native tree — stat data and entries in the leaf, extents in the twig — so that `fsck.reiser4` validates the result as `"FS is consistent."` with or without files. The image holds 25 reserved blocks at fixed positions (block size = 4 KB): 0..15 — jump-area / partition data (zero).16 — master superblock (`"ReIsEr4"` at offset 65536).17 — format40 superblock (`"ReIsEr40FoRmAt"` at offset 52).18 — block-allocator bitmap (4-byte adler32 + bit array).19..20 — journal header / footer (zero for an empty FS).21 — status block (`"ReiSeR4StATusBl"`).22 — superblock backup record.23 — storage-tree root (twig, level 2).24 — leaf containing the root-directory stat-data + ".", "..". Implementation strategy: we embed the seven non-zero reference blocks as resources captured byte-exact from a real `mkfs.reiser4` image, then patch in only the per-image fields: UUID (16 bytes, random)Label (≤ 16 bytes, optional)mkfs_id (4 bytes, random) — appears in 4 places: format40 SB, backup record, twig header, leaf header.block_count / free_blocks (in format40 SB and backup).Bitmap (block 18) — bits 0..24 set for the 25 reserved blocks plus filler bits for the unused tail of the bitmap range, then adler32 of the bitmap data prepended. Round-trips with `fsck.reiser4 -y` exit 0 and produces output identical to the reference image except for the random fields above. +Creator for a Reiser4 filesystem image that starts from what `mkfs.reiser4 -fffy` from `reiser4progs 1.2.2` produces and adds directories and regular files through the native tree — stat data and directory entries in leaves, extents in twigs, as many internal levels above them as the tree needs — so that `fsck.reiser4` validates the result as `"FS is consistent."` with or without files. The image holds 25 reserved blocks at fixed positions (block size = 4 KB): 0..15 — jump-area / partition data (zero).16 — master superblock (`"ReIsEr4"` at offset 65536).17 — format40 superblock (`"ReIsEr40FoRmAt"` at offset 52).18 — block-allocator bitmap (4-byte adler32 + bit array).19..20 — journal header / footer (zero for an empty FS).21 — status block (`"ReiSeR4StATusBl"`).22 — superblock backup record.23 — storage-tree root (twig, level 2).24 — leaf containing the root-directory stat-data + ".", "..". Implementation strategy: we embed the seven non-zero reference blocks as resources captured byte-exact from a real `mkfs.reiser4` image, then patch in only the per-image fields: UUID (16 bytes, random)Label (≤ 16 bytes, optional)mkfs_id (4 bytes, random) — appears in 4 places: format40 SB, backup record, twig header, leaf header.block_count / free_blocks (in format40 SB and backup).Bitmap (block 18) — bits 0..24 set for the 25 reserved blocks plus filler bits for the unused tail of the bitmap range, then adler32 of the bitmap data prepended. Round-trips with `fsck.reiser4 -y` exit 0 and produces output identical to the reference image except for the random fields above. | Member | Signature | Summary | | --- | --- | --- | @@ -12242,8 +12273,9 @@ Creator for a Reiser4 filesystem image that starts from what `mkfs.reiser4 -fffy | `Label` | `string Label { get; set; }` | Customisable label written to the master SB (NUL-padded to 16 bytes; longer strings are truncated; null = empty/zero). | | `MkfsId` | `uint? MkfsId { get; set; }` | Optional 32-bit mkfs identifier. When null, a random value is drawn from `Shared`. | | `Uuid` | `byte[] Uuid { get; set; }` | Optional 16-byte UUID. When null, a random Guid is used. | -| `AddFile` | `void AddFile(string name, byte[] data)` | Adds a regular file to the payload area. | -| `AddStreamingFile` | `void AddStreamingFile(string name, long size, Func openStream)` | Adds a file whose bytes are pulled from `openStream` as the image is written. | +| `AddDirectory` | `void AddDirectory(string name, FileMetadata metadata = null)` | Retains an explicit directory, including an empty directory. | +| `AddFile` | `void AddFile(string name, byte[] data, FileMetadata metadata = null)` | Adds a regular file to the payload area. | +| `AddStreamingFile` | `void AddStreamingFile(string name, long size, Func openStream, FileMetadata metadata = null)` | Adds a file whose bytes are pulled from `openStream` as the image is written. | | `Build` | `byte[] Build()` | Builds the image fully in memory and returns the byte array. For block counts above ~32 K the result will be tens of MB; prefer the streaming overload when caller-side allocation matters. | | `EstimateBlockCount` | `static ulong EstimateBlockCount(IEnumerable fileSizes)` | Smallest block count that holds `fileSizes`: the reserved blocks, the directory chain, every file's data blocks, and the bitmap blocks interleaved among them. | | `Write` | `void Write(Stream output)` | Streams the full image into `output`. Writes `BlockCount`×4096 bytes and leaves the stream positioned at the end. | diff --git a/docs/REISER4-NATIVE-PROFILE.md b/docs/REISER4-NATIVE-PROFILE.md index 6df773c89..bca5672c6 100644 --- a/docs/REISER4-NATIVE-PROFILE.md +++ b/docs/REISER4-NATIVE-PROFILE.md @@ -36,7 +36,11 @@ workbench placement of extents in leaves. This placement follows upstream twig level and other object items to the leaf level. Stat-data must include the lightweight and Unix extensions. Its first 44 bytes carry extension mask, mode, link count, size, UID, GID, three timestamps and -allocated-byte count. Additional stat-data bytes are retained opaquely. +allocated-byte count. Further extensions follow in mask-bit order; the writer +carries large times (bit 2, 12 bytes), flags (bit 5, 4 bytes), the plugin set +(bit 4) and the heir set (bit 8), each a 16-bit count of 4-byte slots, and +refuses a body holding any other extension or bytes its mask does not declare, +which `fsck.reiser4` reports as a fatal corruption. Extent bodies contain pairs of 64-bit block start and block count. Multiple items are joined in logical-offset order only when they describe contiguous logical content. Holes and unsupported body plugins are rejected. @@ -57,30 +61,66 @@ unsupported names rather than truncating them into different names. ## Rebuild contract and limits -Add, remove, defrag, shrink and layout rebuild capture metadata before staging. -Existing stat-data extension bytes survive. Mode, ownership, link count and -timestamps are retained or applied through the typed metadata fields; size and -allocated-byte fields follow the rebuilt layout. `UpdateMetadata` stages explicit -metadata changes, including changes to the root. Existing object IDs survive, -and new IDs are allocated beyond the retained IDs rather than renumbering live -objects. Verification includes identity and typed fields as well as extension -bytes. Volume UUID and label, root stat-data extensions, -and empty directories are retained. Edits and defrag stage the target and verify -its complete native namespace and retained stat-data before replacing the source. -Unsupported or incomplete native trees are refused before editing. Shrink copies -the original through if a supported smaller result cannot be built. - -Reading supports multiple internal levels. Writing builds multiple leaves and additional internal levels as needed. An -indivisible stat-data body or directory unit larger than a node is refused. This is not full Reiser4 metadata support: -separate xattr items, arbitrary object plugins, symlinks, sparse files, shared -objects, non-ASCII names and alternate node plugins are outside the profile. -Opaque stat-data retention does not prove the semantics of unknown extensions. -Native tree blocks discovered by traversal are reserved in the wipe map. - -Validation uses reiser4progs 1.2.2 (Ubuntu package 1.2.2-1build2): native images -with 1, 100 and 5000 files in nested directories plus empty directories were -accepted by `fsck.reiser4 -y` as consistent. The largest has 166 leaves and -three tree levels. `Reiser4MutationExternalTests` additionally checks a wide -nested tree after Unix metadata updates, add/remove and defrag against -`fsck.reiser4` and `debugfs.reiser4`. Synthetic tests exercise stat-data tails -without claiming their unknown extension semantics are validated by the oracle. +Add, remove, defrag, shrink and layout rebuild capture metadata before staging, +and write nothing to the source until the staged volume has been read back and +verified. Volume UUID, label and mkfs id survive, as do existing object IDs; +new IDs are allocated beyond the retained ones rather than renumbering live +objects. Mode, ownership and timestamps are retained, size, allocated bytes and +directory link counts follow the rebuilt namespace, and the declared +stat-data extensions listed above are carried byte for byte. Root stat-data +(with its plugin set) and empty directories are retained. + +`UpdateMetadata` sets mode permission bits, UID, GID and the three timestamps +through the same staged rebuild, including on the root. It refuses a change of +object kind, of link count (which follows the namespace) and of extension bytes +with `NotSupportedException`, the volume unchanged. + +A rebuild writes the captured mkfs prefix back with only the identity patched +in, so it is refused, before anything is written, when the source's fixed +blocks hold anything else: + +| Block | What must match the captured profile | Why | +| --- | --- | --- | +| 16 master | everything but UUID and label | other disk-format fields are not reproduced | +| 17 format40 | magic, tail policy, flags, version | `mkfs.reiser4 -o formatting=tails` changes the policy | +| 19, 20 journal | blank | a kernel-written journal is not replayed | +| 21 status | "consistent" | a volume fsck marked damaged is not silently cleared | +| 22 backup | everything but UUID, label, block count, mkfs id | it backs up the root's plugin set: hash, fibration, formatting | + +Writing the default profile over another one was observed to fail: +`fsck.reiser4` reported the root's plugin set disagreeing with its backup, and +with `-o hash=tea_hash` a long name keyed with r5 could not be looked up by +`debugfs.reiser4`. Such volumes are read, not rewritten. Unsupported or +incomplete native trees are refused the same way. Shrink copies the original +through when it cannot build a supported smaller result. + +Reading supports multiple internal levels. Writing builds multiple leaves and +additional internal levels as needed. An indivisible stat-data body or +directory unit larger than a node is refused. This is not full Reiser4 +metadata support: separate xattr items, tail and ctail bodies, arbitrary +object plugins, symlinks, sparse files, shared objects, non-ASCII names, +short keys and alternate node plugins are outside the profile. Native tree +blocks discovered by traversal are reserved in the wipe map. + +## Evidence + +Oracle: reiser4progs 1.2.2 (Ubuntu package 1.2.2-1build2) under WSL. The WSL +kernel (6.6) has no Reiser4 driver, so nothing was mounted; volumes written by +the tools themselves come from `mkfs.reiser4` and from +`fsck.reiser4 --build-fs`. File bytes are taken where `debugfs.reiser4 -i` +says they are (its own path lookup, stat data and extent units); `debugfs -k` +is not usable as a byte oracle because it hands file contents to `printf` as +the format string. + +| Volume | Checked by | Result | +| --- | --- | --- | +| 3,004 files in 28 nested directories, empty and deep-empty directories, sizes 0/1/4095/4096/4097/2.2 MB, names of 23, 24, 100 and 255 characters | `fsck.reiser4 --check`; `debugfs -s`; `debugfs -i` + block copy for 316 files; `measurefs -S` | consistent; height 3, 38 twigs, 113 leaves, 3,040 objects; 0 byte mismatches | +| the same after metadata update, add (nested), remove (subtree and a 255-character name), defrag, shrink, relayout | `fsck`; `debugfs -s`, `-i` and `-k` before/after | consistent each time; UUID, label, stat data and stat keys (object IDs) of untouched objects identical; the update applied exactly | +| 12,000 files with extents | `fsck`; `debugfs -s` | consistent; height 4 | +| our volume after `fsck.reiser4 --build-fs` inserted lost+found (object 0xffff, uid 1000) | our reader; then add into lost+found and `fsck` | read completely, 0 mismatches; consistent, object ID and owner kept | +| `mkfs.reiser4` volume, then add nested files, long names, an empty directory | `fsck`; `debugfs -s`, `-i` | consistent; UUID, label, mkfs id, policies and root stat unchanged | +| `mkfs.reiser4 -o hash=tea_hash` / `formatting=tails` / `fibration=lexic_fibre` | our editor | read; edit refused, image byte-identical | +| a file carrying a large-time extension (mask bit 2) through add and defrag | `fsck`; `debugfs -i` | consistent; `sdext_lt` still present | + +`Reiser4ProfileExternalTests` and `Reiser4MutationExternalTests` replay these +checks wherever reiser4progs is installed. From 7c179fa9375fd498275228dea25df97d10e3e941 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 9 Oct 2026 09:48:03 +0000 Subject: [PATCH 6/7] * refresh the application screenshot --- screenshots/main-window.png | Bin 67598 -> 68152 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/screenshots/main-window.png b/screenshots/main-window.png index b122a5b7006b79659cd5b19219b8459fbc7cf53d..da3089a3a7cdda2950fed53c1de060ea1c3b58f9 100644 GIT binary patch literal 68152 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%0D03$U&rdIm8q}?R|JKq1iHSLN-6#3 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 KPyoC2@V@|SR1psV From 1587dbf08b5458174a9832f0d99ee25994657541 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 9 Oct 2026 09:49:51 +0000 Subject: [PATCH 7/7] * refresh the archive browser screenshot --- docs/screenshots/archive-browser.png | Bin 67598 -> 68439 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/screenshots/archive-browser.png b/docs/screenshots/archive-browser.png index b122a5b7006b79659cd5b19219b8459fbc7cf53d..45f9f057b978a0f75ea4ee7f011c93efc86f11c5 100644 GIT binary patch literal 68439 zcmeFZ^+Qx`zc-4vL5HN0(hbtmGJte0&z%4L~oKBRD{5ZM<#Mo5=ggq z|I(UsBf&e7WF*AYJtwzjJhbsTNs#ws-XWnT(?lVCK$BJxl6bk*%(u0@byF0iU6x`{ zb~>vl>@9{ZDY2zpmX4pV`7HgEQ1|S*JYne_>|MsYx&9wZk@6NE^t(L{2Mv)15OBx; z|MkCH08Wu`6np6GfT!l$fdog)pfYtMfz~}eq}~FImxa<*9RznuAU!* z!EpZmjVOTeuG7H_lQ8GY-7o+z-2Z6lv+t-2%TFttO1!D3m3ISw*2$ZcQfxq0;HW3)~kXC~CfVCLqE<#AZ;^YErCUlV!IcPc-(6?ic!M!&Q-IjGD6cc)53} z?2YYvML*|V_vDc6@<=}lfs3)v*}<1bTl)r@+C&ZFO>>N#PbMcPS&NEZVNjSDJFr3X ze|zi}v9Rc{Xd28C(a9%@Al@ps7&hE1t2M8-7$uO0%EJYZd%Y5Z~tSUB?z_^URrt ze(s7zzt{F_-w63(|D~zojCB^3|Kq)RwqwuE&(Q@;1#&Zli2ZM$B{FPhMebjm)NiQCm9Q_zUU;dB^MZ`5oFkd%;+ zY#Cc|j$t>%$H#R(Y0YkGYH~1WU`Y^-Rm>f8Un1Mqv$e50JG;On;f!MfkJ39iRAC+Y zYh_W#nrlaRZ_e**FF=hRl`<&w#%*hPF19~qaecD|6aCiord4n3_G@J1<^KC9_XFSxm> zn4CL51Sr~*Vv!54-u{+xS5&O+OV+pORo2v`gZ=#Zvo}GIor+2|u~>L+1v_Ze-Mn!r zV@-p7m&A`kSfW@BheFi#sIJ>mX?=a;rYyZAEp4D<;7wo8y3WLep8M9$KnYpMt3ub! zLqc}w0rxv%UA*>?>$xbIl@IevYp2WBNFlF=M+VpDYEOSUnObubQ_}F$wn2wS=jUIu z#V!fGnIAQ1gf^Gt6&Cs}g>pFzAx`X*KR_T|fga}@)sMfbYzpxfAC8-uTdPR*h1^F% zdikUK5%N|j!)d`-l7(tav385aQ-iiIRl2sGF?)8`%-9?I(!-Bo;Vy$~sFeEVJ64q{ zBbXgZbzQ%0Ji^AcZ{W#OO>D`0yqwN@a0m_Vby+dHUbO%{{V8JbM>{r|w7jftb)Ui2 z&~;FlzGeT(Nt2zu^?7^K!&K8nMG8@`tG(}1N-5Lc^SAejm2!uXkG7%57j7HYDk@)V zcZ=WOzvy}*E;u!}c=iGIQv%X)s8x!A>B(;RP&Lfn&sL{!A}#8hEb0qdI$npdoBM^a z?8nXSZhc4VwGMOVJNAu>ZcxNtIvN_9I5KvV^S0MSY{|@Cyog(u{qE*Wqq|{spD-tr z%@kc&;aWd87azLMbzp(ZkJHhjTZLn|SG`9+dEsbdY)njyfq3c8j7a-Wi571u;p3D2 zYJ2O<+`>z>K(GFtnFuvik@jD*lNU9ZJK#p7+-ZK_{2Ol*IrB|$YV6-&d%ii{H)sjj z)Vw^qm~C)5wG}NhX9=+IOWKv!X?wCB-} z{p6QGhh46_V2sQo--LY*4sLYYpfSbJ(9q~nP%vsj>*?*jJX=59HGL)OcOHRru(-l} zsab5}HWq=ey?!sZqr01tfuT&BMe@;)$u4Iw3Fd0;85#4xstnEuc+kOKV3WSt{r=;_ zhrjj<#gmb%%W5))*HJ%xx(Q2VF{&ERQlTawh!(rPtah^zA9>30b7H6!A|qq{J56kT z{VC(EeM>=s>}8GOn2!T4L{?6&84A5T-oE}3=*2If&qhhZ%vGqXM}(Zdb*m5;pMXWO zwRL-jN=d`3h(n6o@NESf6B9E9e$!*E*2Q)cA{ctKcef`YuI-dhz9@Q;nCXa1+b|qA zey`{Ci_3$?pIKyBEDl--OT|~7b>ePlOkDq-!$Et13JdoY4~%C0qV&;mXna}F{Z;)h z`4o;|ukg4i7J7QAgQ=Sywk#_@n1v6>~}Un9hyMb7BHLX>cI0)>dbNi_`UL=iT|+=%4l;d3E8J>Fr(s;R0*gvUuk@2?)7FK^03Hw>SBzkk7MP;U`;n5I=a*K=v= zUY#&F+2_8JaBNdhfaP9XRb4f1cTz7$G=fl9Abxrs^$iUj%?#C3kUkfz=2TyQk0uKa zTQ+(c)vI%0mOT9`y}YvRa+0x>-D_>+Xwm8baq>kWst z*}l;4uZ4Vx-`p-_?ie&OFfrW}rSEKdBz7-6dHRHb^GSuH0V%BN=ty*|=vce5tFMGW zO=kUyR;i`Y1C^4Lm!4fIEW50XMJ*gDeiv`*TQ{*W%3^6MMiFNSeX`HsI zj?0U;dvow+-^1X*HU5l-(ydKpV`Rh7>PX6Ob7*AHdT!J@r2SXi_=|MqC*+UItZcSVV2upQ8Kd& z45@Z=FiZ(6HL`8ggyDD#P7Pc3kDd6aFusqbo$ohJIBlEK) z+Piv4DUpPU3-PrbT)b{S-4*pIdNNX@Xg}&WdA9E&@8GZ_I#w#`d!Rnr{84dtxUxN< zJLUBH8u@7bcqz!s$!ULWd|dbvdv>oGARAscnK)v*=Cri5sw#C!Nl7t9Hg1-!={7m_ z`toxYp>x})`uue9;_d3cu@q9}ixSn9V>RugH*_~61 z+y|7T&!9=3Hb|7YOykk?XpW@1>rs=WgG^`st4EJEwTfSJM#HrVOFRu+T+7(?G5`+P z;G1GLGz6$Ks<6r8d<7f%%+ri%7Fll8C8^ot?GuKDAUgSnSu> z-*P^|uxDi@vE8J;JpH&UuI*pExxUetvfm=ARodIv$-&39jYv1>m?|s1KFe)iwHJRC zy71=3_2n@tI(DpyF;J;uDLy=_s-%FB?=m#aA-!W01qP6xE5`5bd`D*^XTGz;iFe1e~u$^Vb5v#DftbREp?~rZXO8;y}-%5 z1Ihp@)9^}Zc?B?)a9(Vzi$B;Gtgtc;zQTb%cvpT=c>EX%>6_9~F<(ym+Qg~qjvUEf z!6JWs)4q1Q00b({p;glS&xas|(ec>U`Oy@dSDu#lut?)`lRmGbYO2#cvnSd|J?@Lkm*D*n1A{2HPxWSiTxQC zBrk9w4kE!e1*Id1m*`{|ZUawSUPC^*NXgOu@^k;@d>a7n&^z0jX7{PAZ2VA6TkU?a z^UCpu*i%fG;ha?qizi5;q3*qdFcB#U%ZvAN~ z#mJ}5c~uEvbM_*sN+@4rW3}!sb(pDgGBxw>r(Q&%bYj#oZK$0uAGzs8I?unALAkw* zN{w)vWY-RlkYqJ1I-@|97k3fg%ty|F#Me5GfqZ! z!E{r3$|mV&~WU}SVvUC-e62^O2Oj|v2yx4pf9t8r7q=DM$j=E3G$>@J zC+Ih<8&8J{rlgQl((v(}Znep^gpnWrULTG^AcDfkMSYIqx)%@u!(^hrrf2xfeB9be zKXHITP-|yFzIXLjS63h!hme4v#fR+d+(ll@IK3oPQBMjPFkr~X+_CCcfq|bsePU!} z>|(JXK}Nn0g+e~=ZVQ$>Il*u=2ne`_b6O_Gm2FRs8gfc=6TtvTh$(3`t8pCT;k_=iL~vWid}C~memr5*N5*Pf z{Z*ttqo8PbD*b9%={~uVj0(raH8OKB`yn2#k?mcK#|aZ z$I4Dc#>TF#yY&vjdE9iTZucXLwzfosy{Em;%6!StY47o^ERLmkK)<^@>Q294-bslnxiz z;&C58t?n}&76?<&j3Jad-DRyU_ymvE`bOeD&@VPd;8yRDL$%qj|s))Dy@M(<9(yu=MM`!^L$DlqU>zj#B z_#)u-6!M=aMRUA^XJYUF%)PWJ^Q@tCx7_?fo$a$=yq7Kof!j)|HbG+pV zW+)um85kSu_6kpqOe}0Q*?;(#8;^#VPg6nxlcUBVd}W*Z8#Ui(huTg=YyAj@Db6 zTiQ=8e;*u$Lw9}WH~BU-Z=SZV$;ESewQXMHEr=7~;|F4qd#+3#pZoQEr{@ETfq@c= zAnQq?=Jh2>gJ0}6JXp?~oLovcCIt`eFaU-_Sb1YKEZWPr$$EZ|e(6=kn&ZSf%TiM*e@PW#HMlRtd;K;{2^ zcvwzGfrrC^2I?Y89#*_M{ay8PfPE@pVXgfDnX@U zYP#g?mgO1*NSM*H=N!C>{T*+r9Ld_(__(<6P!>Z-cp@SKV#sh^1r!t&L%%DBM@B9= zzd%f|u4h#R<>p%2+e0t=lCPSs9A+Arn3>1g{eRZ4j{_B0idyJ>gqp(?z$*h5ydYC?AJpT zhlgmNhbj*8xdaB&6zkL_wL z%fwv~D>|K#nOTL_+1VKu91bCr*4~oNeL|JS*29ZP!M(GB{y4bdlCpuW_Cd^sc<2qM}wvZ z>K0ke?kXyj$7vR}uQ}p@UM1wcV4^H7y-Pl`?%L2VnzCVod#fg{6{}~R3^cc|()jIR zxqUb>{8gI1Qu_ZzhK-aSi~1dJ>*nT)hK`=va&@}l z)78~wy}nNMHE*#$-bP6l(j8t#S$jQU;e0ArwqdisXf0z34_SnEkZE8MY)oqV5Ke;qqho*; z9OoKKf2^o`N$N1pq<60XNYU(ln{Jdk3&SB-5~Vq^2YdHK&;z4qAH$TMbMcs$$7Ku+ zEk;BP4JBZQjeY-KUtfR!;#fgO;kXeR)O~&s>f4o`_KQgoIa<0A$o6dVZT4=)LOW^jZI+-> z)zILv9~$o%c!x98piv0$S91Lu6%EO@3LP%rtJtWXUz3x!osngGC^a=Fr7qDbjAK}2 zyA4uZg#o1rr0q@AVB{ zM+fNI?DhLU|3{RHi}gCl_ZwFRVf^;nIGQNykXHHBF);A@+=LJ4o+y;eF|7P2W2joS zZD4eVD12!TduqT-k|kYG`44;_l@e^@u5(o@tt-sEJ-uCq=0~5$c-n7gD<#Cl(nlO? zw3?do>We`4?QIvtogD$r8BADHQS^KC!q0Ds5}x37uk|H7A3>R~e~ z#TnsyKkTW0a-NI7dGn{m-Ijwai**5RyNa+8!08K`IH0`_3im2b|4`hYW-^U4y4xv~ z__;pSOuI9J`z=H03*PAt@=7TA2fvPF3=)(EDhZ0;8-PH+xM*$58;CF6x)ywu@3biI_I#v<=V4INC~13c4^DmF98}dHG*W-0a;Gr9Ye>Rz#xZ%v{waxg*f7M}(fB2b zj?0x{ebRF_ewWSoOO07~NNVvaZvoi{)Vn(zKcjr`PL{_p<8Zz=*EztcGk&O)Q5MCi z?D@;1yYp$jG_dHKz&-C7U!1g?irVLSaX4L3GQ6u)UU#RJMR6flfjDWZSIb2StY@LA z7Dj@6nRm~HL<5@>ru^xMa*xT(pHU|T`-e?U(b|vaA?$|-0KhdW)=DOc3%F`9a?-Ko_&{JvtnPJv^7s|FasxRHAkVhw}*JS5ifPZ zWydQ2=UG42cvSAS5LL<6a7F3CxQ!o%(~%F(DB@)|oYzt0@qQ?~X|ILC+b1Q@c;Egi zdJnzC_&r9H4!+RY2wp3Ik+)@h^V5aUHb{Ts4Q5WzL8A9){s|0|l*ZTYykBDrG8cgt zGmLn-DT-}F0XKeXcC`vJg(3>*>CJ! zJ4B@fphuogE$ByiP1WnlmLI*+B|vt!9_%Ty9uCBx$`N8^RPuYm&(HtxVE{3U=VqUQ z1p8ui;I9VccYrNS5;XXlCUN{x4%$c6T9|0E;x(mZk6m$X&JA7o9Pd=by@-H z@|UL^q*A7LLpvJhMB9CH4Ib%i`LMf}uK0yNcWD1$=s!iaWD?tUQiaHGbM}2()2uo{txocthZ6F%;0lz z#czb8gu=1BPfE5$@D$^YwewJuvr{s1u<|HqDD-xI7n2GidHnR8SIDFz?fs(Tx7ZSW zU<>I;N@jn5P@`-4keOC0*ETPIqPB*afyL)yi~pY#fuPS~g;jks$k?i?Y7`>Cp&T@2 z3j%@#g^uRK3NAiAB2i5Zjp8ar0Kn>T{~K$!So|5$y<%f+6LG8fw79B81bT6`=r&*W z-5-^*)UXANJZ_(ll?eql0Pgqd`I<_Fu*71Y&BF0Wh@?@N~~V-LL@f3%AK zXgvsk)YNE_czfI1Z`(AV%1*`ni^01A^9Gd?m|$k+mZ$3z{;x7eu4juTTnB)&@B;qL zw3mr3NE|Fxwkxm4K6^2;D|ycU{u*=>2FSIYF1JVGW=3J5nuC*!oAcqp|8%jnTdd_I z%n*RT1EW|+N0)S_rFOdD;$+9|^t=BipAXZG0Ee)!>1B#=ztN*1tE`ab>uLT>!2IqMz?H3 zwBRpWv)Pq3HSV5fS-aa)K;z)Eo?brh)&kOsfPg+kTEdJ*$*ASMIrX$kW(1vll;*;j z5obw(OysYxgM&}wt(sTIq%hdjn3F zZ*6^NgE&C6t*7GTE!INGcBbnKi;I(i$A?F>CY0s}efpYGCAF|Q~lFgKK(?QwLG z(_EQN@?}kkQJ*nzT~^>;lBRf(Kt3E*K=6!S;pW4hGL(pIk z0cK7W@!J^~mC?|MPEUL7IL0I>C`i2|dQB@wHc|L~OT8rnw?$T?Aq}t`3kwTXXHLrH z!?h=0r4i%f+P`j0p5H}teBP&(g(gL>l{}{8r#$T2ig?f>dZK&aDb&j7W^9m)Gd%XzlLw+Y9bAmv$^b zR*tjzmT-rvxVf{g+Gw*hh{r)Uxr|R1&dfBP?C`m*CS77*qf*Lrc4A*$$JVnBj18M| z#8>7jEghayh+fmLMXA$Mats1lZY7v7k=rIKBZD)~ucbzjGr4(dusS$2v@hRft&8yB z;fHU0V(112<97CBY##9LB>H>(s%mQaT2dHJHC1KGs;a~SR?1#puVifsSXcpSID5!b z#Raot#jJ^W6+IFa6^%k!|Mo2lVPR|Sh>$>X$;B}~1{4NV76Ix(# z;pXA#7AkHC-5N>LMNK~|&qD7}PvVqCG3Ja9eDnyI`ubS(RGdRqGE1NHGox-6*Vj=O zm!k0r-0JkH@B6Q@8v-f?6dA#4r=q%IKyg}7vHH6Da&#fd89>P2o=%5;>(+V*Nsr0N zsm>0F(WD()P4}H-^4(a0ipP|j89NZ-llH7QAGx{t1sH!bt|aPlOUH^9f&0p7mCRN` znUi19!;VNei|l&OU}tn83O|(|et^ktmT5D&3e3FA)tZSJu;Px5=h7!^z$70~(vSP& z(C403vwI2w&6$JolK_3u`LX}I^p0mRlz;&B5{Bj#uy;wLY&J(#eL)<_@2lA>9LpYx zE4aC75JY}ToQiTZqq+2&ZMpqsQqGM)I4>_-aZiqarN$>9 zpw-Y+F_tM%bFWE>vQg&ZY0Se2XsG&_Q{fzNQ$;e1YC%)4x zFtAxeT^*sW#jeFADtf!)j|{>VL&Ia19o<22%*(xXyyFO<8hlMRwzgis45KK$oF)}E zsoRRhiW%ay`K$iF3kuM6h5H7@-1+o9n68C&DXhVrqynDv6X-<#5Qx|5Z^hhU=*_;m zmRh8`(f=VR@No~^?(XOxa}4JMRA)jr?+1l_mUFF30L91O_FASiBPretij4aESv zowe~)Ap(lCjc2pIo+))rw1OZ6b>>{N=Q$G>b7NiI3u@{m^Z89|3eo)hf{pbJAuP|B z*woa-#7pk^_@gSy(1}BVs zKW3jD`#e6}VRWM$f=wK4)guqrv?o zD;F#$D=SyH|0}y{+wIL82M33C_iX`>t-Y;@!=no>`ucjEOr~2Vw&$F%e zwfA#?%Wm~sZq7Q@ASilzo7b1v8atJhH>aZDnIC-c+?T%W&1qXDYXs{LUSveg&h*T$ z?_ym_ugtB1*ZPe;Z#I>Z#pB^%g(XUFqI6^J`+H$yr5>eYV&4tNN@G7t0DUhdJYuDkVcTAaw~5haewq76Sb=w{WVd{y5qD z6!~ORuaboK<~2_vhj36YIcM5!{;=f_Dr!kuDsgfDmEf5zy=p30k#^1UxDmbn3}J3| zE*~!kxu^wqW)H0;L&Qdr4%~Gvo!W({4g_yJ)~7qoEwmsV0M5_w+1I~0m*sM){H*nq zfQx&;gM9pUx~+RL!mUtqYrN9q=qDpLac{Ys?HE5-b`T^c2BZM4YzpdRXA5(&S#`%X zFCxxTmcaI%kf00lU&jS+uXlF69rs;k!?LE`GZvvJwh=yF2DZlbk_H~|yQ$2z#{Pm| zK=zzp*7Hr;i#T%%IjOdKk5dN(yIEcxkAvM%YuAPpR#fZ9jxU%btq=b~hh7(5`9MA< zRB#@vo9>&u;yMg07ws6ALiARkQp)G3;ziPN`ydCfao*0u1mHma5Ef72%Oz zpn0Ju)|IqBlgP8O@LV^xor!Y!Br=@IQGTvBpC6)I2nB(%%$S!sHl$~W{eZh=vGFFc;FnuU|+aGga3$90Z}e!p{DV| z<4-a&k5vXaXPFv!cUTSAF@O8L10HhQ>*oSBwt8~0ksP*@M@wt{DuaUrerab04tuO> z?0j7GFzw%)cn}2qCL_X&$fl>)$5bFjw*V`jwYn}vwA ziQw2FF3z6bguXFF{8OlCheftegHr3PH zn~^y;(*Tu{g?!}3247=7_JFL-pTV{b|w(F9KzWa~HYpRSn;(?3Ywo2^=%;lqatm?8h+^qgr9 z*427Olj_#O*Vo2+rsZSOG&Ud2Dc=X#oRpU3{Gy`i@t>ZVmjcD5_oEwViSsu~CYm_4 z`VhloO0tSyUD+KsI3*WHWGSsNx090oA)|-t^(R^1;ZdLW<;>d6ks!o7SSz>$YwTU_ z?S@7gi&*3}xE&Wl(wr$K7`ghW(E@!D@1}R+!3Qk+#|?mGE9=FtxACTyRF zY0W35ONrC~RBdKMA{ zzAI^7;9Y1nt=H6mK&XTG20$8ku|AiW@bnYudfSt6N z>BcPCJ!DcP->)Kf=;2e=TwF>h(x0z_bd~V0fX!9L@}11KwQCym7%UBU!y5(3O%7hR z=b~TnC7R^vo2cj~D8z_|k(NEg2_|xUBajTAvOKi zdg|&17ndEbRU$(T1ql{Rw`$T2 zxw4`vj??%mZ{f$pq^_^W4oU#z=L4J9vl(q&vR(9Hq*rT0=PJ~9p^ZlzpxN>;?}Am) z%3Yp$Y%iknMHTV}(;o$M;p2nN2n{_9WV~(6%$7mApyS&!4n_`Io@xUUdTQ|>v8DG# z{X!WTSP&qSxcNIRBLkO^{P9yVVb7O-Hke>=5eb!IP%IV|WT?7#S*fTvwV8|le~Knq z!?%a2M)2BQkS%=-QP)uKkx#s_Fcpsm5r1q<^lltO9mzu(-#^#Z}oZ0arH5j+u|WACRxCpAv+@BSkuux&0|2WIU=eD&<3b zXRHS2208wriQLstz1+0$@Q)v*?Q2eaVns=Hrwopo0Q*?l%?!#D_;7ZXp4o07;w`JB z#rKb*i+FLaJxTlgz`)16jwT^c$&uWi<3^rX#KPK@k{~Ci6t;veG+MOi`~rr9+^7kX zs(G`pmVCLQw>p-+s%v@bT@j^W*Z^)}gVE;QgIn5ag6GTTDwxAv~dOIrL z`U0x%dvaiIqnl6@Lr6(;G2<;vMmB<0KLD~HOl*avrLo7)1nK#-0@@bQA8gnZd^q#u z(a`YqX#&Wn-Y)!~Vis|6{}6P@ria18ff3)}zLQmwb8|FNnFz0yC+2e}=T>H5soPmP zlrft7S@$)|{xM`n#51kc1d~D_t1R#LtbfqZ+_PxCQF&>^8q+2>pRKxiG6}qPTC3E` z&cyj-FCt%9CGjZHN$ z|MD|~I>*WmVJq6$?&OJ@`R}QjZH9IuQe%n*OZ3k- zKlYglX$?0|aWtt^0R&La@pWcyV_E%T#q&UteEG$Lr@$ zLcU;R&NgRmNP+x`g@vv|wQ9`B=;$i?g8)g%^`}o+6ZqE9AHZYb)iX zdE4mjim<1d2 z@;>5ZWd5Lq&B~J=d7O$8eelTdB76Wblt%s>|0xV7y$sCZ7Nyc?+nF`_+quN>@U5++zU?VI z$b%0LAkrSUE1esPEE{dnJ-eieE zOIGBYU++Bg1xxVC@^T(t3UJKBq)=q!Wi$Fnw21Fgv@|efy`AY6cJ|VFAd3P4;{L4^ z$UdkY4sQ65_v|eM`&2 zf_D3aofag7s`8Ggk`_5~GFm}2%Ty{<`6>bH6EN~uR+ddnmqeg9(^*cYZ_f{q85p}a zHaBPdewTK<-a9%5y%aBRVKuG?Qs)Y8$Id)p1Kkzz8~{l-VJI}P$>;Y}8Fs3O3j5Qi z#KcdDL5&dn_H4hQzju&2P|HrdT6&T&xDzB=2nq3(`HcbYigqmfh2dH~l$B!2Jup(R z-?hEH^#tSBQtrJ|3X!{r^N8JlD{d&R_^N#3G*YRqEx$VO{XWP(KEcNwYLxO|w9S#8 z^n#dVNYi{?Y9}H7eOoIWT`LyU4fZ%0Y56QpZ5z54tk_^`z}~o@->ogpnCPO;R}YR) z`RVAG1%Xd9*g5#V)2u?fh&L1q#2eq9pPLhPc6BLA%iFB|&dbWm%E_5*h-TGbgFZ^| zB&0CGTA|4@`&{{*+2g*Eln;EL;RPj6Bu>QFWTG1t*)c)yP~4Z zexrA3Ru)W_80;QzNytwa>3&UDSy#u5tT+dM{yf`!`)zoP8dSYpP^LS+^@>iyVq#-t zWM$QO{e0P~3fzC7?D<_$Qj-rsTy!)m?k9=t>^553?Cd$>S29yTZdh@yS4tJeLkj>k zbL@ujf&`tH^8N5R;2#f2S5DL+DaQ4RK`eKP* zDZ2{DH~&;m5iQCL@K>J4o4_(QCXY~B5lT=<6HFhi5?kNh+iUKd{6z}h^HL3nwM=?J z>OA)qgo3ft^m#qE3j8K;ot&jnZ*k_P>sE7R1>CJ}SC2{NrfLrZAixwzTpb0o7a^CE zlj8`10F3;?&b>M&`d^Yn6*mtmrI2tlNEcmPMAN`PXz8SQdq>z)TU$A;xY*uqnt_oq z9tXHTi3u_8hc!lqhH${;#}vs3@ZrZOjMjRv@Deh?GtswR3q$5~!R8hg2}u!<>mo(j zH#RvnfJm1r?Si77xF;lfG4c6ulZgmZzvMS=dPy-Uvw-1T#SMNztDOGdxrL}77T8Y* zZV~I1#$Uf?-~w^+`$3z-NwIEcorR3sD2q|U?!ec2^y!1c!$#MgWzNdNC|(@MXt8d& zY5(jXUM`Sj+eeEH)_Q(^2kSI(3X>vcv)9EyMgVK;kpT=D`Ln;yi4#A;u1|7wGIXJ0 zt;1M6|B4mle-1|cUWGmBQ`aOk3KeB$zWk8826WTBB<1k%H%mFY<0{m_!NL7T4f^XC z+yE)E78NJ(I(#X~cc9>T{do7Ro+=?6&RvRsp5CM)Yo-;=5@quqKtaL=$?f<0Ck3k+2|lGf65^WQ?@A%3 zfr0AMjo)u5kRMdJxw5b@6}-6LJJ=W6W|CuR)`W_+K2-jLFU95Eqc7~zi|a0^Dn%6u z@$czcHB2c)d^3ivE*>={6&0j}nZS#TCFpM~?hJ$mak=O1{^9Rk59#6o`gZ`hib= z2P+fFKlK;Gb!Wf5d(g{|6u`qVG>@savrrU%-O)W&TT|$%#}pzO(gef+W0-DjktYdyWoU;It3{`~U4su)1h;?IdYV)f_s?mr4NfNl6eosiY; z@q?qsNWYlwS{UhHr4P?2X<#^Ek+=IUFF+?58730~Ahj&n zjwqN99{}Y(fq`&VHbxF!2X5!_fmz{(u?@n(+$VB-g`Yl!OnL7qD@$8BIh9vuufWPp zbtkz?LogNMc%gZRX}--~l&j$8JbU8(IFE$dF!CKlS!{M zh$%W0`OdO(jn&G*-0k8gM`<_FD-5y<^Y#*OcPh?zMn3 z2)fd4}SDF#<4ndXuKd3B}mAs;{MSG-QQ?^n;xaMG2LD&_^|&vf|C9|xcQ6)mFcD!dbq_Hm_R?F0l&zJw z<$AG-pj3>DlQS#oABvun3!3cxy+I{#*zSeBe(hxY*y(6;8|>8I9QYPd5~*uC=z{ZFW6xXMJr*9G<2etx+{Ay?-hQfk111jPsHUp`C`ItZ!(Uvz z6Z?(74X5%$ZGLR-%6h+7%(b<(&W83@wcP`|`GX)B+naN7pp561idMF+Ps=Xx|4)+9 z^|OU#HxWK70-xgbdLA$PZ%kO|uN$4YcGb3IbuXjt7oKT5nS)lB?hb2Z)mn|9pJp64 zdiVhl#etH>)m_~Q;GVOL zloDD}Qdg`Jz_PQUr~LWSsAb`}wvH(8tv>!&>8OO5oa}TJwr8D;o$@&m4Gmsx@8mw^ z=8VQ3{|9O>GvP{U_N+hsmJV&K0ZsP!zwr9#r$4304;rnS$>j(mH|bjG^3Ywc z9v%)x>&?Cyds0%+x9DhSp!ghEu7FWPezbzBQ1$gd49^SJ1U<)zdM;{ee|DTLLD#H> zW4@4Adh?uXshfPES8i0GtcY1~YAt4Nb-R%cXzcq9Sb>i&dW(x{wx%`Mo^k%FR7FMo zbV$f*z};UxEi?H~Aou4)x|e7S(@xg!O(Tha4|0*h|67FE_lh(USf~BRn_;O!w6L=3 zYG6GBu==44PzuC1>}Krr%;xJGgQ>7&A>p?dJs(;j_fvdJ$!DfCj;X`N?q5*6a6X#3 z^bmsj7nAT=`6P#ETAw#)FuQL}2zo8z6NjvhH&kyX#lvvG2%6x!IbGYRp5W&PWi!bF zRp@U?G}miJOLZ6E)|=s9P4f!BfO;mrCN{^lJBE|~~2WV(s8yldBGA?hzz~5(xDbi+SOE2MU_y^32@#y}g zC6q!g`cEs}`**HegcH*LRZjPf!BYTyHZo-V&PRrW0DSQ?x%{P&AYg&mGn^rrEH3%s zf8WpHwe9?WmdRd7o`hVCOS#F4&%EW(1Y{KBcsweoAcCNjhzLkaNQZ!ebV`SGH=8aMM5R%>M7q06rP+jZhop3O?7PtO zo^$SZ&imbQ$Gv|B!vVP2?EQ=9S#z#A=Nc&nvuo)ua7ER4eqU<$+^UKT#!{+3aWKR2KP)@yN zI1kYm&-{H`i%|aal^*Cj`?jwA#DSF;o>0)~U*P2ABC9vdW%-0r`{A$18T1Qi_5^lt z?0e-OMl!1^D@<8$I|;~Qkm;@7QsdB-j(H)Pb3m4+Lf%WiE4ILnIUbWT^?31l5&v8En{5PsuFK4W`f%nJKbTDT ztQ%V$7hfVpClhuyVeu*bAZPoiiIPU5(KA4DmB+W71e?XlYDv$)c;YgJlM_HMt%|lnh zH#avc5DNuHr|KD^AR9#3*g4|j95Wp9c7A^T0vUtfo~=J>{;Vq3*D@;u_l?*`vrm;Bc4eFdy)yU6e{rQGanx(Eph>Lt1(;d)uT%ibP2DoS#4 z1@3*9!K?$msXuCL>t-ab;$b7LISu$%8P_JebWa20^yX*mmY_-Bf7N7`iOITfu7g&o zJnLWU*7R;f>!3iNxks`aRr*P2W7Sj#g>melejd^l$smL-Dk-T^>ty;Z`VK=li{AQk z>+%n5&D{+lcjb3$iQqz1FVPz)X$R09x>8_y01<7T6{)rLnG}=-T(2=_B=W zn}goVsR%ujJ+@R0%X_SF=u7Dp7+-`iA=jPlovDYZ((Ar-JbL1`7?Y z@(Ow&tJ7Bc-QT@^EB*9GPohY2F2lcO2eH>AnzFm5NXOcY969gh#41&~)HiPKt&Gkg z7OL`tgGV;;;H>;h1`wH-g6e&jXv_WasGZvMaV5sR)gj$J=e_Q3Il-q(6YeetG(p&4 zS#9a>Ui{WUP07Z_&Fup*HP1fAW@m3B!raKU4)4;NHH0 zqE{5Rtz9CUK8uDew~-Vd4ei?_oUh&8-TqB+hO_uMf+?8B=c6guOTdCB^lVG*FOaMKi*B7h$OG-dGJ7x0E zet5=8e@dz$wjc+weM9*EZvxQz_0C~1D=EPW=bq5-0N9$Io<1>4f(eH9ufy-8QbD}O zzrLq6SMo?pIl!d}!O)F5q|60>0W&L@Q8d|$`85Cz7KQp}2NHM6CAl6!^ykFnm z_e@^C&NG$Y_D~^1-I*w3S%=^fTfuC8gacXChmn;bu`doqus?N9})=~qvleKx1;P>IE#%xm)chO-2 zs20V@{V=tv=}>f%< zhybw?L~ zNmhf)7i=|tN>KljW%>)hFY2J-zR#^zthxfi!0=uNJX)&Mr%NLFG)=$mlo9=qkoRYO zlNw5|TR)b>ubqaoDfBgTCu;PAPI>P(N0H@#J-7(Pn!Y>yT-uytunp#1%VRViS-C>p z(J(8bxci{@S++8&qcDQIQzt^~GHnxxMQe*M~?a|nKC!=sUm z;#E6NXYuGT*de^L{}P({R_?c((d{jqw;Es3cVne{L1&gw*xlRt;*7 zd=!oo3k2e;JK#$zRunIueQR~#+9{b*$+ZafoX}4%M;UL8g7z!5Q?tm{Caa(QNzQHQ z=tTK3Xa;}%PdZQW+WPl@YDscR)Wj3J@Y~~E+D8m0zSQMgyMjS^qbC)_R^#Ok@&C@Y zH=tTh8y%AVbVx$b;M}+2)7=YDTJ3=YxVqN=gb zf*}V2z)Ze#{c85BZOzS-SAIXjA_>0jN}xnb!3;?}ZPpr(&_$mRPO=g|?GDY@sv`#HwOQG6RyiwCsyIPD5D%ptch3(`k^J}A(mWRaQVpSzCHiW{s&bq z^OnYfdt>fEw;p}*idxg|fm8WZDtI~HfQj+ZiGJ5td;zrm77Vf-ib_SbJeP?YP8P#Y zRFgy(F78ji0(AEA%V&?}-;QrZ{P8OOM!mBs)QJQ_)S*D`c{J90pn=+~SoP&?wrV=iQTXpi*D)7l)AR;NhK&7*zAle9il^6$P(dvG!_ZzI7O z7Kzhzt{T0&oKAgv`M@_enw@K~G(`-uRL84L!hxABXT`w=qOab z^KzhHK|vlEU%ard*lzi_vomF!o16#ZICxTWaa0sF+q|b@_NG!+an|*tz<%ls5oy*LKFVwS#ESw zNuM`jje_MERZ|TDRM+yJ#>N9!xzEAR`0T4C~uT&9fvm zSreBMwVo(?$iWeToQ1r~GQ@y|Rxa)0;v(EJI0FQr%!LF>cp2BP2M?RA958rr6qUR$ zC~X~_^H+1&Ug}v2jOVr;rs#e5urc0GUK@o!=f@WmYZI{(!<5c zRfU1!e)A;G@Oj>6eJR*I6r6@Qip+aby6YPL8D=Xl>K%f`0VVq1n=|So=N~~tX7l6= zX!p47osx%QD-J(ID&_jW@l&18H;!$hq!srCvLkFoUzCY4H9Ij8LXdzK3B$(+>Q$|D zts{cVsq=-<)aB5|q^T4PvOiEOd)mE+)0cb{cK6iAF8b<9?OZ3D$ryOJNCA;ivG=mb zr!S+qhsC>sZvE~10wspFPR8bT`Xj0~i4OlA$@Tb<^~pUO4{FA6g^CcYgQ_%`KbkYL+QAuPbJh#bdt+XK{7u!*Iwz;lj>c(gEDA8-R7L@ zE@zoP<=gl?f!ZB&QHa&2RcffOzs*~Q>PIl*>Q0wm8GC&9dZS|OFI(K~WLNFxrq`_I zaLE_m$uDC~4kv6mYAe%AE@1235C?6BO)T%o$W{UL_(-?#T?5{7yB+`7sJleU>twU` zB^(z_Uc5TG2Y@TKD(uDwo14uKE^qU(I)bHV3Ks`VFp!=gGLU1(b#j0V*TO3KJCH>x zs+E}dzq`D(S>B_@R(7;M7FP5Y#A^m8mKl3}$ZD5e<>K*JE=BPnHGMPNcvMNbM5f~W z%uF1YGl&h(gQ6S~e3X_r9-(h+Zyrv}v}nfNtVAr#ES7*JCQ*l&hF875zHw#PT~8<$ zjcEJ+7qZZklm4OpVuFs#`xw#vSYFuh z4_h{g{py&~>|NoDjgmy{?>9P|KW>WB=?Vwrl@!Qx5?YT+(B zmQQwOVxi-gGi4S$BJD6yBp|QIs8Nq*Z*7E~k>BHcRXMD#AWeZ}OCJ%yH_&QX9j~o5 zIbe>f;qz^ioy>0FA=+jkBJCNwzsbeqWjb_l;VQFFxgGg>owOk z6K(Tn79sX#F?Trb*}XuL5)u+Z>mX7~`r9+s|Jl2elTTjDerU?#{jUkVhX~(iw=doQ zCvc~^c}djgijx!g(=XurCO1D@}=OYx(4(qpbf#2DxF=O z!|(Bv&y?alT867->#!J@0h((`p&_@oY%cCBIZU8r741t>!v{c&)LNsE{mQCAfFhlM zpE;?|xwg1#vf@Dw+T{G9vAi&qKo;K=U1Ij`VeR zC*8hJLw$xfUnb1}nB|0U;*8O6--56q$+g)4BA-;F2o*SAeSxNfoTB3Crb4jn9mr?g zcRfdzq!y2oczmP&3S>TgSk; z*G$pKID$bJ3>)szNJ@?4P z&gl(wGPp0nZCXHliKgb}FXV1x9b{K#^uK^`F;#HR!kg_sJ~Y<Mbzj}6A{mINV)|L;Uzt_`O zy)bWRuCKC6LBf!llyr}bDVS?>>nE13%716sw}F9jkHkmkDnh{3Ozq?QPv-*k7}Irr zUC##x2OwbRb=V`d`htgv>922bB~~P0W3z?{0^q1Mgc6_eT~DuIB4xJv1{C^STa4za zQli`^8snY&nspMuZjw2SD|coqB@zFKs0Ku49(NhuU`fIvMY@HQK$0`dvTLXm(&CV z1dNT--@SYHGDh7o!- zL{7PaBS5YhyJCy9+}hOu-9@OoECs|2&nYSbW^DZvGrA>Cl$HK({mS`;C6qyr&Q^d1 zlmc*N!s)S?o|IWgr=jLSZCrN6{SY1Rad_;Vlr$r?GScE$=D54rcPb>JGyQu*0~FMs zpuZRV26$9s%!@oHK+TsfkE*;%>yLQ%t~ElIJOJfZk(TyUY~as6p`69WVq;aZ4+|6EE+G6W7eZhB!rzfL#Pg8454o2&b-AL^vs+1y?# zpghtz6>CLs{Pi;5*L8ehKpjW4dXhKjA`+G142U1agef%AB^+CiO!B<<>uru!I(;Nx~O| z2>`G_rG^WYTD)%ePYgCDEcR;L3Oa6J#tCog*QomxTsOA9MRL4^es#SRVHO`BPRoFC z4XX(9YMaq|RiuB5%T_NPU#n1Z^B{++nuNu~5%TZ5Qqd-^^EI8Tx%qSQ$0jlJZeOOCOy9c)dZ)5JcP+56&}8O|N=%ELj->Y5qe0H}Gntn#M;{zro?bG-lng%} zpm{n=YDk)owek}UMjsyCa!&v#yn{zV_7fBT^ymEisuo*a;UZzd`sAbE0WN<$bq|;p zOhm)vC!1MVN+kLi*1IbykGA;cu|P~f&ABO0BUV47V-wyt*J(u?h{Vo4GT%Rt7e4sy zT*W!GNwvw5d!M?3K5b;~+!3ZjJ6`-tVD_td=}CI&X6OqoS$A#GvOChEFZv(*q~8xj z;h*7jPp#hEoo9~A^`db~D1ltm)H!N~>kWqZn$B@n40mLMG-cG~-Ua`9$tm$xuOB@4 zT)W&;QqtZ)(VdGCN$Mh?5OKO@%b7xqlz2OR=O{(`1hIT`ZB4c|kd)JP@U#1g`>bcu z%#8!@q^h?qlwPblw7aG)U9w8``9Gf&Vm0oBQo7ILG`!o?VYo40b(252C9~CZuxsW9 z-ot3zDB?pLz2HzS;v4U6+3B*=l0IPDF!GGah!1rsXi8%Iq7&drl)3ko5s|#&jja$` z_vFF_6d&XL`*e=VLGg(uJbvE@W@&n6BPA@BY`k@;q-<_;d~^EtNU#}lQC8GOykCQ7 zQW)ecSt|KcJJIYO@to1o;RBg2d$Z#BT?=-C&1M5mr-8R2S_9hsU(R2Wh-D2HPP{G% zjIL6yt;flne*l)leh?W^iJ9Bvd?pyr!^3M`P^T_?q|h8Y?(a5;jE;_rv%p-uM#@_P zd11M)UP2DW-#}+lujNF}O7zFcyf^Ei$S{q)jr2jY(qv@aS8~NH%}Sk};kkar)Ke}+ zwmTpwk1jF4;&}QJWNsrxDjc29G$bX(#1|94>c~EJU5zbGH!g2b5Y;hA&@#%P6tB@I ze|UkDFyLo*8=Biqoc2>m%NW|)?(W(a<qQhK*SOd%iJ=b_xP9^0n|#V7UV+S$#_Jc{06juZH6xm8hsD;n|U{dzlABAm=e(BveH}jOUO!`uBce8cD`fEb1_sgwd>a^@|^E1sZ#w$_ZNK<<<_Np> z^!(R&h`v8d_kRZI{+fIotlPbjg$m`t2`?nK=59xqCZ(n}Z5;HDDha-c%dvpH9i?79 z2kEa62t|ZHSMe=7FE6iRhl0|ppf9}F4A53K3CT%LQqr*s^ZDDkeU`Q5mcwUW^q`0` zZDc=Gk-ZCFE(%Gi#HFSiwJCPXqB<$w*!AK04~ZQ{y`u?XWUVk6J2Yl7RC2v~_Bv&9 zPh#ZnPH{^Vv9hOcTHx{r*Tlswk=o+ZtHp)Jns>ET0qq9#POo@0Pk*k;T9-o*OE?lh z@1q|hrC_o-s|}P$cwF`7NNpiC0C+bZr?0Gj?MrPR!iA8eKk_GhkVG|$)B?@WuToDXFV+ox*BR^5*DVyOvv{AWDE z5Y?^1Ne$mgaUTO_4a-sf?lEsq-*<@s75=3^-_3ozb|3-s8djFwcFp03;(gbm{q!a) zd2ZZ?zrzmxF~FnX{G;{$j%Qc^Yki9*EjwYqz8n!L`g6NhIn0A^NY zYf^Y+D0-1?J-LHty7%%v*6!{q5j&(?X{v~pn}&DTW*#!>A(n(zWaYyw^^e!61)foi zngB2mmOfz@|tBv5XzE1OsYoSe{cQVxAwVi3>KTO*91(^O-%bb;r!AC>kU5^g$#TC+|*I@zzi%;j*|Nmg5%#dhl;hd~h^pBamY zx(}#0ZUzS9A)DFh*V|2G*C#LSbCTXF30R>$eO9gjY;=snrFxI@wQKVYF^o@Lgv7+Z z1$zdc|Mr0RV5uy3Vnyg^*HNonU0Hdi#7-r-uhEbA`u5Zik}8lbOU?Fh^Z>?Gyb-SR zZKS)Yq_01BAW!9%>*7zuaya-F7t>{((OT^mJbcw$4H??0Rdg2CzFE$q#25!|_fy-Q zVMdIZx%Lf=x4_bUcbKk-j9geQ&$%1?bXS`^Ju1n-wW)!EUM+{XNyzhc4EDQ~(L$x& zv%V1s0k8JiGY=6JjdNNB<9aK|dBQFedzc{oPSyKaIMf^@qBxSIL zg@ra($f+nO^h=g_;K!G)ky;)X@{NdEBhy7w#rfsJtY=9kYt)E(LZEe{t#`;}8 ze}w%d8?o1Xwphn-I@IiW3Sg~pOGR+e^W4Qb`AsI>H=LKskI2-*q}8Bsz7yeeK(pR6 z-x)#!s@~$Fd}9m!oz;^|j}u=!A%93u&&FVJJlF-}hJ0Jhrk~duHrWfl8R?#dI0#zB z++yRE2li9;JF%2#u-l-5sh!)z z&jT8REtgGs?ooR)YP6k{+qzqSmyUq;=G^>Ea5Ra)J;pJLPy^~v41>vgww19cw{hZne;Xr0kbM{ZMiTn?~R||=EhF3kwHMt zx%*V)EG*QVL#^E*&clQC%A%swG+e`FvWa3#klkDL)7(w8$aCxLHu~1x%gW5kW3}I0 zKjFd7&JK3ev`xv?C7o#hpo@rVps8j7$ z1xpL50tZgYi<6-y7EJ6qrKhF`H^Y?GAVj3??rbfM=D)h^i`XbIMB>wf3)ZhLr6O^O z=u#>j(!|K<1MM{lKmT_D{onJf&3-qE!1$g&O$Gd=2ztKp$|LkhR&CcK#r9-I9lhrg zMaBgdkA~7zR|w{o%?4V-u$hgI*9NMp*G4@a*sb633w-^ZNJPp^T^+L5_gpPajE&*5 zq7CL-%$W&3{mFKIYg(f5siX9w(|q93+}uS?P5q>y0kr5^TUBOyMvu@FF5+n2k&%&k zp099&zz-0*8f&R=EtUpYY-wB(^fiLY%Lb^qP2i0@*WV8D-F{z^pQT zrLR2$6J_ozB^s5?%*<_3Y$UuUN#*I(I#*<{5b1sp^JXn-Zy)gPa;8o@6S+uPdps4+ z+ut`f;11QuH&{9B%f$A-h>ywOQ&?|K7AmM-`MvP@MmIQlE;3tl=3j7bCCsK#zFnCG8X8%a%hVIIp!+(+m1Im8(SC+9SsBi+s7cM zSiG8`+_byGU1G_I^VZeXjY{fdVl0{+=sIjo&jQxozH;J@k-P_sbcet>0>2nJgW~-u z=Fwb><1JYk_BLXjG=c&(xbF3-+hN<%$2*mZC9K1>TRapb@6weTaOrop?ZCBCt;YVa zw{K}|WMH6Yq}cq*&i;#9{#ReX;uIPjd>!Y|{yUEn%?=5>b8p#kJSL0Y zG|RQ|*-i5y5v7Uo>RNqkm5cUg*9XDr){T$5Co}0=1t`zF5{;_m1Lg-0Ktx%e-UG>y z8bnjec6CedRJ0eddM)UMdxv8kwqO&1#39J#J zrUUN#`_#}_+U(1Ot{|`5uIs?K8vv0%xUZML1*hG}zmR4$h&?~;5&zB)4z1)^g>EVc z1V3m|gocG(o|?8C-(+EV2$#DuE1=r>wrJFHgKt(AOlgS`k5hsxsXZIQpNUKC?Y1ky z$^h%VjcHF$A53LBiO(DWWN`hmH-vtw->ksH$M5%*Ifi4qb(3*xb2GlyaUtIc`Qa@t zIL}{$87*%7;7U03;6b(18N;omt}c-*&5RXo;-9ltS(c=a48;}9=(~rO78!5*TE=zy zOV}LctcCb~;Z^I~RW1`R(tg|*I$y`jT{8WSS*G70a3^!1e2-)%E3uA*$xInaR)qt~ zz@6w)b5drLtNy~aHgAs?`QS#RU;Lp^Ay_4`IRpBm^#-na`IWzc6CWGqU20 z`QFjt5z7IAtyM{ndz+iB+TZb$tgK*=;d+pkLMCP8)=fJ4sMy1$h$JCv&VM5V{c>++ zq?7aHo)$V+bMHh{eEoW!Ukeyum>8NhlDE;2sl=tey3eF5?0-wDZ>ZXBsU)`Y5+;$R zf`XaOs1fqwH4}q(es#if520-p;47|LqRtuMgT^n6r!?%ivzKkL6DZ+E99(eY;-$y!97U`?uLorXJcVYZv$gIp!y1<&1=FUV>i)^^TD*wC?11PHZ9Suf`5^3(p{ zx9+J;S>v`r`>6Q#@h6hXy<^)kwfi@qOHHR$psXgIsUAzThB ziDIce*I_mF2iBb1ehor#)z#HUzYaUM)?<{^7T@#oJ9Im4{$6)+cWK~r#fWatob63U zS&f6kA$e;jF|pRU74F~SYQbW<2oq7{O1OqWi&fPP9>xK5xV9k_PsQ7Gydb5+mqvPkLg4LCX+<14ZZ+zUl|dq1%d4}Yd=-qw5_x?0t)n?`jN4^um1Yo}&m zO{_9%v$1}rXb{Jb=-#UsC}TXEXAWQdwHI;g967tFR=^ZahqjFB+d z!Rw-*tFEA|pe%Fel(t#2Mcg$4zl71pgI#3(gq0}#RB3@oKyiSXDdweG`TnLrI@mE}6ivz*p zS^b=e`V4nihkfi#(s&tTU04obh2TOg^EmFmDV%3~L!gc`0~3!& zL;~olW1^!gt(Qv1&tEKF>Nd}=6mfUw3t&WhXthx=?`5sqE-49&-*Gtuqt*rY!($aW zeaQWW)gJ6nkS72XZ=O+7N;258{Ykl1eUSQ0k>?7mtw)PP+O)GI1<{cjS4KGF(yofK@?&A)gZ)EQR+d!Fia%Ms_Pe(y`$y!%7Et%d>>oX8Sidv(^qw zYE>&*mfoPeM_*&Lz)UaMzNExsyJd5eUPF^qBR#x>{w5{31h=XR8 zYaa~cBGy66p!ol*cGvhPIY|-96~--A$)J93LB4>2mLk~;o6vvPQqw+i=}bUrXNg!0BKNOanxc5cOO{`3{7 zuAY(bIXRv6)y1C#e=+JBt$L-U}PDuB_j} zC|oHii-sszwD{dtd3InBh?gm>mg~ly>2eeqeVuKgAL=gO9Fpms#hFSG3{u^mw7G+*WUd z6JeMVH0sE%;P3tziO2qwnp$Z&kF_EG42x(;WPlw@Y6#$=6nbzq+$<=ZucEk((oqk_ zQ;LPY`5G#SKKDccw?)%$rf<45^Fw)Lg=uo5K9F~|BoMeEG||Tb3QjzlFfEr(9AaG9 zbF?bW2XcVbf$$uWKtf{+6S3-~)J$>S^|Ml12XAM;YitCvM!Rq37S)8MTzvNR`2$#g zK7@wW@;uYKfer(!__Q9OFZ8Ec4$WaqUFa2wEn^aG5tT?Oc-=lN{Jusg@lrfl&%fdR6o53$UK%~$HT#L9CR3Wz1~ncY_E(}LV+GqqGoNqutU-H zr*c3>DR)1kj7c4fosp3r46Q$LNcYXapE?X_aq(0eLF*GO$Qwa;l_$}@D{|DPboUs{Gy#-Au>Z-eSYz969I|1IVi zE%k-^`JI+jMsXS4`AQv5vd4{x-#(W@^6k7n>YTQDrQ^_+{`2P+n&WTfQ*m+MSJ*E; zMOP`dsCImvR316>EO!aA>^3$qprA>(9M9)cW4cxY!xsL?K@@oLRDtG5xYl|Db??8a z2EN+#`CY{{i(=m!tfJtBNqn8D1}kDJqu)M%O!oJ0nrok3M9JwbP(lk0Yh@%`y>hp% zNg9+h;klY_KTzmRH$4e&vDF{!Po&;W1U_EAbal|!(3EyoNfcrMKsy>;dfX<_ega{zDBqXbEwCe;XYEJs>aR@3~blVp-T=qI6wxA4g-?PwwDDf{F7d~G6 zKY?uq|B1Nv)c7bo?sjIPvNYGEcvEun9-Yk9s)aKwv6;DUEQW&r%DoM=7q8_J_LdVB z=JTrpb+2J68%!r0F8J~)D)}zdz6yWUz5&x@p95tZLfJGy4S*#1t+wH><^j@mM_Zq~ zd+Gs~GS^?tgY%AzAlw4Ncnb!H>^M1EO@?>*o}m;O6#vkzTDKs>N*8zXD1OCSE|NI|l6y%k_7SQ#}w2a0--dUS= z^{g}S`I?NXs;V)kzp#+i&xc}=JrW9gA01CccV8(jr&Uhx{vE2H?frXRXqbwM_7-v` z2C~-qcZCwnoODc55^k$$ggz0G`q}lZjni8C#0-t^&8kD`hD~5ar}zBcPizOxw}U?7#dhV?9=sbf zzy=j~*l|^-mE9wwAP3#-tkQxG*|qZSo5MA6&uZm1X>nXymLFIvL~n^DyV}`VHc*dNdBJ zgVw9dDs=c)UZuxDcs0yDk&@~JADo$F@x_DN`UERz6H3rYm_fBW?i}uT_jp7{M~1pQ zyN9zfSPe-8=u&V0pV%vUQLy|0c>A@B{nP7s|myl!_%mva{!$L6fTRCmA zdSVNv_8`hePu-5%BG^KuQppMX{9iYvw9hkWehPCtSvC%p3`tH8S&ZTJ`2IOKv9(pq zgF$7qM3N|DbM(TMqn(AGZ1H)qXb3AaXjsVS>TM{<1GEe-Pu`b!%@IH*aU z&J7YlHp_|Y>&H)k>4YLMtb#tN29cDUY&l%AvQRkx#M>LvO6DsolvR|Q2PNhHz*UXs z*v^$9%PIf`I%A0NP49Xg>| zFK(Knza_eJLta$&iHfL(_Y?A><(R6MU+>ocK6!M^UO!~}PV8NXChz8ewN~wj&+ZDO z@0RD~J$1FqRuC3$oLOMGa^*6L%g(diK;9daujtMj%d_uAi2AcR-ByqB7#SZYUhGny zDtYjup>S-Zq;>F~N|dT`EUf1zK3FKXexaZre4PK4unQ6-kp9E^2b`jjP-!{ z={Z6h!TTl9IT@k_xDD}1Z%~(Q$gJ$^=|kLReLzPSL&IO@-~iKwA$&E-dYbIjC5V}M6|5j_ zoRL2mm0>3N4DpOIRtkx{si5n`XWa_mw=;?L%&httZ8(WV3&20;Pp9I2N*yCM!c_rN=l-544S(!4=l2PM2>Ec z;~$uv&HOAK&#$EVo)OzGnzf<%+w--K6})+!=KotyXqsDCNbbF!u}LyQM@N?kg4>x{ zji#T+M~8<;M@NT+xvw{OQCztxW+G&ws%oY_x}Tp2ETabnDz{7 zLyJ=mD-}#SW7U=@xPOg$@_S&e5OnZ~N-dW9Jf4qqXc$&@WI8J6M2EbWAOHPQXB-pI zmN19Ypfkc9L=})vBluKdyK`g)(4*YZ#eJzUeDJ*h_6Ey1?3 zGVS7v-CgRiw(s@3HM4!!mKe-qhSq@%t$LZBv6NH?NLN4v6AUeVvj(w@s%|)&_zW5; z&Ck_qr^-!76mv}nOxZ%H2dXUFVtJQxa;1TK3d>QaUcmB4dq|G+HY``Q`ZFRYD#|1o zkC~a}eaL&5nVp^r1Nk7v{nMvJ+J4u))4H;^@3|4nlQjm#FJB7kyV!|ZJvA~?jxa}t zy=mNg_d1EPrCUK1aTW6_pX>gurg?skjgSUNRiuNW39Ra$l9Q>^2~?w^K!AP;ixdM3 zgZ@6(_hybiPFtz!1G}_V*$U%Y; zMym(oz1`jLX&1?2I@~tYXP!D35lo(yorO*YEzYq*)FT_Q%RAG`o@-M-HgFG}4`E@E z5S7d>611>jAbSkSo$4^=4R$@Xd;^&HNlj1ETdUO&?H$MGaJpT-)jM5N?TlFHz23F2 z*%j0a%PCEI0Mu6Itusv1R5Y}-xhe;#6S0+`{moucm5gNC0+DZG^{b}gv*AhlN-*Cw z;P$A;cJ4G%G3Q1-73?s6r0glC^BsL$F#*JUS^B>CXRhp|%DuITn(va&Y-3p}HNq`m z78I-ba8kgUj5=0R_otz_^Ouwu)KqC`#vCTG{e=^!KqBH5bm5B2_KGw*fUL1nTc7=8 z8j%mn=hujuR!Xlv_}7k^Yh*tDt;t$2`MrCnHZ%-GY`?wBRY#r;-P?iT5g#7&-X6@I z@T_r@mzTdraW>l@SF;^B-jr)pv(CONt=S-I>U zH@C=>pUzf2|j?5!l9c8Z79LFktB z9&-8WpM-$^g^iaL@z^+mB^IN3+FSiEgnU?TU6!Ymay{Kle*Kz@Om8?#1;7ajsU_um z#KB_U1eJ8P&1p;$!%z0%KaITefzZesFgv>?|63dHj=NZ5*Pz+%2T@!YA$$Xu$k1RI z)SBD-Z&~I$NO|*eVG#K(;hm>a7Vcus!sUV`DO?&-IThsh9@3k470g1T(JIffT|to# zN`-bctrQ0~HYQ^W^A*GjOgF!Fad%B=V>|J$zMwwE8DjoWB=uM8aCT~H|KY6`H2z19 zj~~m~*reXBuBWJL@;h={jsV-*8}a|N2lJrAj6nuzlsK2Y4N_9@287A$%};qK-*Y17 zfE+A&i|R~nL(P4|bgY7n#cZVb%3t@Fcq|YZ5|JD)AWjARgAhQkEMYLhX8x7@ZgMI% zxew;a(RL&Zx3|wWGlu9fpPtGo3Z9V~jt2|xX%`Og5&f0e71(T{W%Lu5ZGlUcKt2a4ndF^}Wq=Xa)% z2C90p8-0}04*h(Lw{JoIYCbCHcWeCX}X=C&Um8YW~nxn~>;Bz8Q{>E42^ zdR+B=kK#N?KM`<+tHj+P#o$e&gCA^VqBP+rE2A#!52)Bne@-%<78ZtaN*zd~2hO_R zp(8ylHK$WY)d6JYG`F_G@aFOvrzhB?kj(K8`>VF8h)69hHFdBJ2Yo3UP!v)m!>DrS zc1RNk|2`WXjNww#j*O@5*Y}9$+d6JQg17~yeL0(rGwVTODFTD37&rm(TcErFi4d?3@wf@sJ(D({m1Fd6W>xKkVJdS%c=ViNOUtfIf zVsAFk+k4LoT$L_kul-@*81Q~5%Qe2~D6WZ%0TUS5=t2U!S^X~*uKV8Mua&{uI&NadUDJ&o-#+Pk z)}C?h+r!p*rEA>@b$g~BWv)9WQUP-$*Y>D!Yc6MLsG)b2O0S9b7?_O~?C|DhEY)gh zOm-T(_fSe79*(SCbY+?mFfmDfx8_cabN7AAcv_^);HuH#mrKBF^11D1CB71}>V!hl zzAH?n!s_&8gZPNUYDj3PM#3@omO*>(X#v$xv?yuI_T$7zvv$tBfPK3KcbStUN(PrnM&o{qfD0=q?VcCKM-Z^wIMW6QnJa(zj5(sVZm0z&)?m z>DY}}bz4l#%-u#deJUBml2R`&^e&o_lcy`FuA7IISg4~U#$u57d!}!48jFbCA6O% zO;5IzgO4ZA+=A(C_bho9zK}Z@Ru(AY%h{aiJ3sJs%k3Du>|pv27IS-4i3e&1U&4Ao zG_J!*=&wM-6drB7_AtXmG`X95?{*Teb5@tppcvXZ+wtW5j6@7`u~eNf-|0E{Jl%OY zND1#`|7r`__;5KxXjH^4q#mV{mRve3E%&@;Yu%-5Eg0t}&8_GT!+paUPHvR1eO-7@ zeuqZy1JjX82Yz%`k%O-_dT@a(LYx7=UQng2rdtPg2!^?IZxDhxTZuT zx4c|#D;}0x=DZT+Q#q2RU1xH1w4gXUH|Kr~7PijVkqYft!%!ND6dZ76Jx%VYiQf%` z+pCaT{(0p8Mc#WrMSXVP!bYQjNKsUz2##1mK#Gh=Q&doT??pg*lioo?Q)wa~(p996 z6zOe1snVr)hTdW5z1%Znk~hVe_kX|p-Sw^YWo4}phMD=b=RD`^v-duYC4^hEcYMi9 zTU%Sp`cljv#k4c4Or$h#IBqn5>IUD>66I{ijheIs@$k-g8`OT4pz4{K8Kd^ZrcB{n zBMl9Q4hdRh&h0JR?l1KMW;Rp(w^>4~Xa$V!C8+dm{cJbgoZsh7e{yGm!c(@ ztCLr*TP(`GQc-Vfv0DFnbvE}+y8RQ)`$=+B69Zr28ydIPf<)rXG40@~?emJI2hGY3 zb6L$Ed|GGI?0&_xb~r}}y6nB4KH3sd4K+1Jz3HF9C7b!ml_Rv0S~f`2u7brvdCUAR zMV;agiV6NX^DmAv2$-}bPZIdWEcU2=P1Ds%)hL-$PbmQ4`3e1M{RUMexBeU`yRr)x zd#foV``)v%n?Ap!(0Nrud!=-0s=~?6q)hVs{NbW#ftcbaw>C5-Q$mjBUfT%>DF_{E z&!w_Zl}j4GYbL?w2U~~FfikZg!?wk*yI(x5#%Xsq#`oR_M{q@ihp~H$!0c98(R^iP zQQ~JWcqJO8k}?l7b!Dn{5GP2|2`uXX@u^BOa=R);)e&XQs;(X1WMDhjza(9l)5BnU zva>r)IKYEd`JujkPa(ES7?yTQicEfU`)M9)A62`VM45bnCX^{F@vbafho7r4wLq5bMh;4u=cFooD?ck z$nUgbIa#Y{FEy0z(wC0&aGCdJn~E;(Y-x%|C;QQ`NAI7$fGH^%iZJg;(YR*On@6;V z>3)((FD@o_xb_gqM)sAj!*0858`#mI^A&&svqtjgTYq*wU{^&f5mEa51eb595<0!W z(ZcoOlU!~@t8$S^{-9@OtFlpu$ePS$8TOa{2^vI_lGk5G+lygC0-Xyc+LtA$&@1Db zh@H(2)6P8OH2!f`wXcUlz0wRYIpPVV^dXmZM(4)3^jhz!Z-AVmKTCJ|%iG&wx6g-* zQxWsfF!9i!G8D#SlFr=@Q?_=1Cki$tB81tl#dMP2-bo7C9Fs7MSmD0=Vs#NWr(VNg zO}FtVEiKI~!#<<2NQVLfouAuKYx9G#Pzn$u*x7>#-nq`S4#w(ls>N3NmsqZJdsNH1 zY#(r3Awjzd#4YTpOt1t;UAO73ig8H=x2ET8^z)2;c^ksDd)Mr2Ei=;7C%Th#jEuT> zf`iWv9R${2^5pQadP+Ba?yfW3lbq8>5ojj|g;W!LeWdff&aljnZkS^AUe)2NkZp+-`1T6gvpu6dBVdy)2J+4QVFje^hxAYEP0mp5f_3WSf7d;-WbkiSU{h+`o zfB0^{;#lm~^YuihrkGxyc42IYrCuF}-$T_q7Kw+#1+_pTY=IAa2$c@$vrLaO)=bHM;%ipVxOTaG7`0nBKN#>VWOvg?a#FNtkMkTsiq=@q3oh=vR zc57Q}$M^tl;JvREB};^@w|TZ)>~JgT?(a7nYrWi}U%#=GRF4$X%>wR`-ZhF>$f-q}NWly!tA%4%Uv6vlZkq$n@V_E3WqOr#h- z(Ftot-kL+9L4FrR*A!oQQF(sHYCdqmS zutf56Pb@VqwyWnn`V42IqvNW2-EOu2%a;Sv0%LpGS3Y!gTt^=6dCz*?G}^`%T|VX6 zR!>hUc#8krzQ);XK3naT@NAz@Z(;=8beO7w_#3oD~JUvrOMI$g;2o>uqD7Od^Vq#(^8zZI%=c3(0+CRp;12ao3 zMl#=VcC$#C!;?IU$9AF>UG?>|yO=mJe^qC7wKUY7!7x6j6Y{D1CyoRO zE)EW-sZQ&XSMcya?B>f7XzuTqAY`+if3r!?iHmI^O!8L>4nnTf){^NI$|4*98|;Dr zI=ivYWZ|+&Q1L((o3rhSiMz>^3P?9KLap@|`+|!+u5zBc&Uvo0N?-u8hwghOV$03* z^V{c*H#XZzj$D~r$jdL7>MtxPxN!br?WpCmJBzTLU>)R72rd)P7P_{K$tE<)IW-zj zubx&#e$ZJBzN34E`AU8L($3b+mwRC=j*hm|H;mTPv$C=bGxgHeR|_3&rYGjQ-l)eZ zx{lBFt-X9{)RvUsL%ThhP->>~2&?gu{H(c*eKnJ->R1M8^f>JxfV*OKV%P*++5|e8S~Ay zQ9e*5`#>FW2eiSCHrNXu_UjYUUoP&Hs8SVAZCxA1IOs2II}BeC>&;Vjd3JysF}IhzR12IDO2vZggyy;+u5wO|g~>i=Uo+ zq}Yu*Q9{f8aDArP@$;0!iElUPLBu|hhk{CPJ|8#))5a){Q7moq-7{!?^91ss4ZHrP zgCOa+YnCd<2oJgnY_ERdxnEPJnltTUH2A!|T|PTIF2ykgUp6FL`Nft?Hx8yT%jJIj z7{=+U26yYMEp~EQdaLv~gn_4hPPS%!+h(4JyYJN{$qq7=T)u+v<+cCX^m*`fz#eV; zTz8TzW;ceEcf%jItI9;1Zz&Jy5eP>PGX*I{ZC;pT6C)C3HU>Sy0osQci zF)AvG{2e&-!FEtIzlC)z#!{YQohfFJNouWc%O2eeXI07=flvoQapr-!8$9XM{cWD) zZo4aXdU|>xH6g^t-sH$kc!-E?WUdfQ_Eq#M@C_qFQkua_c7$NO%bJ)L2FeB=>;kR^gPk2QqG;p zapdo|dFZO#cCNvdQLU?9#2&YKE4-^Is(&#-h~$^zaNbc{H8tS(jWQ{E`P0TJpoooc z#mPW1qN1!4eu_r3$l7qHQoKOk!R=5?c9&x8C-Kdpma#Csj@lFrC3SQj`B0eG&jTAp z(F`611Z3^YAKt7fm3GAGcn7@lN-MHh@+yqj%D78xYGUF+4wbh84TvK=G0Z)q2;r#^CaKfUb0`v7{z$7wy?PNA|C>9L!V_pHGu8N*YQ5fukxhi)jt3lhqvyq zG&I~UImPSPyY5COu$ny8_1+?uxMVMSufuX@4Hc8r70RUDF<0H9Z-6nzroTb+7eUnf zr6oRH+mmGYyaMZoREA=QoRi%?3+h3D-$W+wH>FF`*@ZbP}^UR2>fEVVE$jfoPv1 z7zLxCkkg$vK)eT!0f`$m4iA7jyhff-$9r(&6cv>rXa3X`dH?u(*?Gy@hmcQyz>iW_UrSApi}TUpOyw$Vk?Hmb*fl3^A}}D_+dm|4*naCmAX|_ z-WgZ_Fg~5)vwrB%32AAn68TsOrwo&aW)Gz+>i7pv_~tvs3;@8KyET6sZ4c|cUd6?J zZiEQEzCwd6LdjpG!SPFO3MQs~DwkxHu482|Gq&Ai?t53>S<~4qsr}H%Xqfq&ka?>f z^hl9`VOP<(b1SOWSi0un6UvM%di3gsXo}3F2IE~3q2`5Frol-f;bw|}Ni_RQNyV#}YGwV!>Y2vSW+o}ZK zC}O@Q_*;GbsuhuA)$uOV_)Avo~p5RyYtPHLnjrO zLX13zO4vN@kHMN#4~?;E zWEkn@g@$&lOe0C}+;xy&iR92x*^XM@FGpLXf6h6 zEEMwMB2|gYoa>8kDyHT2;HYD@1V#5WK`A@qC4zao8p5nixnQo2VzJAX%sOh3h z9x`iU0QqIjU30pBUr0y@!b$gCJ3BkmbjNI?%mhjG^oP}D-ef4bpH;oov_iFK|$na`S~?yjtg=gIc#+5`1>XUxFajGYmoT zWSOL_3K`j!=Vnz+pKm3}0mx}+Rmx*`>~%4G_CAeru#N73KrcC5+q7Ohu(T%H!u*qFmf}N|hcf?R;%8i;CAj*tey~ z>&4BH0IVpGr_8kTbbjvFf^A-qPyn-*^5MfFFVD8UHI6n=UAh4Iy?<>>(b=y=f>$h!2=Rc*Fg3tQ4sMK?~3bGJ5`p z_$g^;5LeKEuK+F^fv2LqdC2CvqF(tgfiT;=dA{Q9b(OR6Fi-2y1MQY2EKgBZn){&*=gnPPpZyi*M6-BWp3bYJ9{I` z{G!JxPHUxQ))J9(#~{B7=+2&{>{dJ*c(oCCm@mkuRMD#hWfX$hGD!-Tc4=gv3QGgx zvhAs>%jA@GBUr$7eLNNlBgVZ3p3L$3E$e|dC+j23F+~eavBEA}F4U)_3LRQ52tKu- zxR}&7C9j#5&Z25NYKH=Qsq9JbYcfgJBb^qbCMK)1zETe_2?do&H!oM-eQ=Y1y0dz0J7n(up1p;ygYw4h{r*0(j3I(g!}2aQXlFN(D;f0%Dg5HE84hg8sm9D+s+uc;7}mRWXG zXzPs(jk{2~z12KI$7i-J5J2xFzhcsy*W`(D(|?KoXb!$nI%dfESU0s6Sj`OW^WN$| z*O*}q&LuHboI@Bn*OIb;@8~<11Mb08l5@A6R5`&8f(;Il&4(0qLPt`+1x-HOXx|VfSThw?EjW@(- z=2`@uWr)3-sW)8Z`${CPTbqRD=cvL*gEb*2TU%QoUnWwVy83(SWK%kl)Zd0m*>k;? ziMR768eIB65*xa;ofy^{Bd*2DR6L*NGNb0P7z%VJ7^{1I((PijtPt+jydW=r#F(cy zJ2W~dNJzi84{-;UofI?n9m+m0gfi#YZJrlS$4|{QrA#|%$a|8*cGQn%wH!%DSmqa* zgt7m$yQ`R{*>T>3uh1q%BPE>topFK$4swWjIrPB1(Hi#^ki8%C@cP-?>u1RQ)ULb8 z&^-G~{(-_+agw@dR%j5~s%)*3BMb~n(oV(Q9N0yK+zF1@ z2O(eGDV4o}`&AL2hLxAj`auu#{Z2eR5y(9tMQZiqUa=>}*P;JeKHQtDlYEBVzJ97X z#pCiJg}(S4Wir|_;8o^Df6ATob;?}M--@4_9C zTM^fuZMT%B#eHM2OyzmQbcHsLy7sT7@%}UF_`^^=R(e=#<|_VY90CmDOz7mmr$t794MzZ9H-Ahd3IM$ zuF||Mu_gKnO&E=`99t&YrQ)}?Sc+a?J=@Z|e!27O4Y54_dVTWz^9|i{(|U=LIMv`& zs#L05_kG30O^ZCspSpm*JJe=p@ZB8;e9qkHsc6Rg}miB83_j|P|To1Wvk;Am)Xzxy)V z*=73Bp}=DOc-A*`)z+Cr>tY{W$*^ek})c?RKZ11YnVvmX!l zjj@rDUbJ0fSM`0uqxAG?Bt{+3EuRv(aPAzZ(bt=Vh`@OzTn)R=Jf$OY{*>!agFaSP zHYqE+TO4C`;y~UE4dyiN-`=#DHX_2r9Fq3dlK8OtCc5>N8qu&SUk z{KGN7>cA&VXe0SLaZ-6ONixFz8Tgt0l_=7bGSxX48#l;g-di*{H&$%YBuGO8j&m4( z^hNQAj~2D<(3!Ce{q3TQU=bcbmhf2-YbpX@Mqe^UVIWxqt3XJTDBug5Ff<&rUg1u| zp2@=R-xg9u>wLT4bQsG-IRTS35o`n|YE;J)YA1@!0b z=s3Z!B1YFl%(}nAvADnBsrdt`qbEWZRIg2CU91%v6FMnhH*=uV2lsv{_G6``#{|%K z*h+l!Qo1H{t$mK^FzFZzJPS-p^2>d3>LV*|p}qO}oW~6)x5;QYzkVuJp89YlWU4wt zBu?S9;^mf<8oQG+VH#RVAM^9wOEl7ftFSWN=Evu@Tj>`84~LErY1-s)tRF#J8gQ2? zm^xCSSD%^_gNZyy4Bhir_wJx?*uX3Tl^iiDz+B!cU8H;!k}}gYCAGVr^Yp2w)tJ5fN^gcs)#LQH zfjoAdot=aT+u1H2w}ubYZ79*W<^X{mH5Oz~j+Tr61%2r40rakxWAK(-d&c8g!0rYZ z;(M;z+Ddzq#_(I^+>&w`!z*i!mFkNf?D(Kw#H&U)I81-WgOG%XGv_~D6TCQ=iDX{{l6(}u5v#{Udz@80^)(Oje_E8)H4XfZypHDOq-1tT zghvhx+`w&W{Fz^03;-{Qe(y1~=ZzR3NWufS6#8LwS`~S9UUy|0X1_XNr>4S#BR{xc zXGY$;Sg~HN{_^EZMVE5n#N1peNphH5(YTo}EGZ#tduD;1D1ao-jMSjFvY4=0j zYcKZ%mM2CgUIztPt@ZPnK0k$9q2nJVf0L_~0$oj2;^&r-s#8n_((k9@eRJ zSE#akg$8WnA~=l_6+ZzW-@b)ye9eJ8e|}U$rul=z(sW0PvA+KJ%t-5*#NF~F*}d&) zEWo6IYo89-^(4Y&M{hy8j2{pkI`wlEJHM>F{}#Y;Eoq zthgAV%}G{OIxd4ZPjK7|WyZE3-4KzK8>uq!sv+1b@7d9%GCEdP1a9aDp>*h9raE}s z28Tk76=9f#Vo#%a!!HK`kP|)kP9y3D&|;ZY<9*4`Uc1KZ=G@x3pqQ!=an)f8I1y-+ zqUzp`kscWw_YpRe<@@c>aLA;1qE{&k*(NZjz4$NDeBU)f$nSd0bV2UkGBr~67&esayu zLIL?gF?Dm!emO0mrNza_K7jfdY?_IR=yY~y3#BQh`0tM90xl1>|A%STH$)h1)_Nbur2X{Q^1rM}ZrVt6DJj%lZnq>s5Ga0$L6oSs&+V)jue4tZemrve540XwNWC&q5 zO`ruDR_(T~+wD#z)xyGj*W5Ah{=P!_RJCfk)sfa57>SzPHZ@;CT=R;#(5Rj)wo+1Q zss3=%vcQ_ll@E3A4LNolwi|!p=9o1#CEIc4T6`@<1viem2HD+&!B8JV!3no+hxcC_g0sH>|tBF~wtC;cz^7Mb;~1|>;A-8+<7Wczl~S_e%EJ*Vj=6r()x~ANZ$)Dd z-Kt*0LRYmvS0^GWDurD8tNue} z=+X-zBG4l2-H0WHm*PXosY_q5O@s54FQ1#%_$$9|#XN-F0cl>XZr3Dd#Uv zW`3mSG+KwF@jgQRXlSH*)s&ytWz1yw5Q#|HHm7T>5C;GzLwka|Tb0#KY#{WWLpyaJ zXT#yTb#h6EeTfn0jzu?1?|)H0zq=_#ch=GUW;zNE^X{GEP!Pmjg z7~n(N$yK(s$rA z{NehSogJene$X_9u|q?jXJU^0^geKwAV1t5!gS5cT8BVRg? zpp$0Ve)zNFN@E%_FNI?BgB~e5c}OLsA2_&T36!Xe>FKHF2ya`De6jK^ zNne<#<~brvum#?6v)o~VPdz+-MtA^U`KPY2J|{_;?DFzMD^t5}#*$ccPE3s0g{_r4 zbxp%1oW_g5PDmPLSX*0)x~}U;#e{+|nKKGdWtS<8A{L`U9md)RdvR|#k~%;S%2@Ln zAJasC$);9a7-A0nZrX^7z3$+wl!T|Co;<36fhJ+=tk0> zo^DIwaA}w+F{`?K{COYvmu&VBAv~f?^HpOP9m3@7t+o7>p8hBE_GlPZlP&XK3UZ62|UovW~=% zP_c<5$+>>blkR*B04_w=`qv!h)BaKNe5et0ZDM9n%bP}l9izoo*tnwQ8IeX(duz3d z(b!@9wGfNo)B;fs-=-_Ln(p5Lc`nO3{oZU(j`BudvVhsn;M5S8+v=6s)4E5nJ7o&Y z9cGaC3BkY)ZW{z>#ykFG&68r=tf?>7xwmRpU=!xMdwg7^b`j|c69Y4iuxSASEm8M z1;o7bIBOKX*Wf4GEd23zOb~;skblDj2b0$6@8C1H0n=}dgCf87lrf{U4tIiST*|ob zn@v6>qwFmJqNezB#fL97ey!&&k){oPUj7$cnCMq$R%H_ zWeicVQHvAHWyC1o({=QWs~j66-F5to7#WD}w3PYd1c zDL@a)4&54OQOmO&O^%A{1NGt5&z7{uaA!y~D4P90$fTl53@o%_u#EN071F;p<)||8by(7(qd)mn^i}DW z%u?AHE@@}4BXrAcBbUWhT_(n+x`F%iS~lS|{&BcR=o9$o)f{8jRNuC;WnSFQY&+S? z=Ef!)CT17HfV(-)Xx3G%bF=g7mna?>FSJbEOOx-` z|A8umQ9&)&q6OTc188|6-+Fx$_igtG!QJ|w!9I7ZtNGr$!q-5^$o4Tkoin7`$X+pR zP+dJY`R_#87U9{IWN7hbLtk9*X=>CBX4Q|2t|HIf(R>v6jt0>n2k{RVM3Jx}Whju5 zS%7snSmrIyC_mMNj?f>i>6VA)wdC`B9K~}yO(V6pr0R(Hw{|)szU~5WI8<-`@lO@q zu4QlDc-^`MzU?>V6=2tz9U2aP>bPTs4u!_S3$BjOv|T}4lG$QDdGdUvLS?(_ zF$Pgm4jwkGbRKXEwJC-|~x^ii5Vp6>d6* z7cPk$r4K1d|6rmUpy0hIWL8yCuUYi4t>zIcYua>&IqlQui^)?->ex#soCP@S(POuyxT$ zY|9gOAi)>AEL92z&;b?2@WIX5G4TjOM5TD-dj*uD3%azxq$>oILd|a38c*MD;MB@}w)WL!iUF(}*zv##!l)2>ZpY=XyZ4-& zco5}4(w}nsx(sn%K2?ql`(S7Y8(H#buv1dHy}cc-vDN{CWtyu}gwjej92_m3R^x8| zHeo29MHKa)?B!=RdIl%0!DI z62%E^-0v61n3%ET=IrKPL*}~WifOfh`fS89-)tB#(}g(P+Jv2>YT%~>&$D+R+LE~c z!bx5KBr&mgho8^#QAt=>7?2Fuq!lk9@ECAoQ(jnDlE{krtJk2_X_c_K7@1?%m0?r+ z$}1p$V$svW*7l`HoWP#_S9pH5jcwd5((S%{kpfc>sR)Yx{2(dom31DwU=dv-qj0W^ zts%Gu@cfulJ@Bix_m@)I4UHt7uPwusQ~(EqgoZIhCLSN~Yq5X?K*F<oSKU z^&@bX#XRm<2>Bl{?-aeoTMhBU(W^y*=yP-Y(e zO3obIjxpaJl!5QJ{%uz?`!9DjeBSzNN4etjza!oIR?p&&CfL^_J^e@Y(f>IiqBqZ> zw!kj}9K<18^QYtQMI|SYzk^NXI|PU`|G+nU3JU|MxQpTg$T;wq zh-ffV`G>42q$LlF+3wJJl!BL&gM)gB>+nsvU@pXQ!I|&x4Yem(@PH)rO#Ep%+MyF8 z@3UuEYwq(@>AJqZqI@G*6CWX@q0YDR_VXhN`gRa+wUP$@QEw%3WcKFV+dXMNKTuR& zxpGBZHv^OvI4n42>U%-kX_oPis-fv)r46TVthX(m5cD{-)k#b@}a8R-ij&8 zPCP6UNpgn~i_w%*V^f>&l@&W6Q_#cLpvOun*#YI#HZ9STk}7+11{~YZFeaOqNCwdM zn(OH)Jg6o}N{X%WixF^AxM}KUmG2C?R06kc>0B-Z+d}o0hldae_0=H!vv&PAZq$%O zByaXZ2Ya>cD9|ED()xi&#K4l62@VxR58#v}?(4^@QIO?29A9Ld`}dA@*nQmRi9~i- zc}T5!JPVIL5xtr9EVKfY7{(oGWJicaMT#~1%XH@IsK&+?OAi-b=ONqCo}aK7-gDUQ zL?!D7Rr;}P?-rVw{xyNM6vheYVXr>aZQCjptF8Rs6!>}t}b;7+h*g%F0k6#2!{38*>y!6syv^KtdeV#G_PeJ(| z?)VkOP?}LwSL^%@=qLr(o8LK7f5bU{r%~b8|8w^R6c)DG8(3{VO>^~fHjsf10_AJIqT|Fkp|<*C|xu9`Tzzv#Cns}*4Ot@P;uLT8n45z6{x{w z|94lWrLAT231iG^*+)%r+SQ7I&ZTi2R^6S)mWgkx5@~&;;eBxPV-Du(r9>^M7ecYz z`s-R-gOzb-K9I5;9&_6O7*ecr z<<+vHue_@&Fx3qW4b@GFa*Tc#avw9F4S~Lw(ePM`x7+SUY>w$`{Ql6d@L%osqX%D^ zU~=j~WXDvzWd_9bZ|?>o4>j7GoP~spbq=sp5Qzf&sxMd49K8b8`huG`Q6= zVETWr0K_kq|JSxk`wM9s!A(FurqQ@A8WpFIWX&3K_B_L+{Zz>OvIoQDv#Mncs_KNL z+;o6fUYYg~{Cw@mWXIlA_O#$rsK{^FPQPU$r=;I=y%|+#hdrT$6O!!fzfhT$y9Jym zLOhM+9~!IXBerV2+ZCb;DM!{87Ykf>DxV}?0xA?p=+yG8yP!%0+9@F-=k5$VC<8My zau2lPiE$qf@Iv4Zc)xh@!X|xNXJ>hNd1-00+y&I2I@-(52H}yPUnGut1?B}GfTyB> zTAS%ZYW2P@J&a3gaQG$pzmTE+p#ty(U4z|JJE}2p@V48|ED}b|Lmqzj_^;v_ZMGk5 zNvoiu1S%!As}KtmlG$!&0}~UqJ6d`;VB&FZaBj7=6n7bb z{1~}RCz!{{Ta?#$yp5fWZL%l94tP>aY0UV_+HirN=3iHU;GLl`ZG=FIv^g8dqd@*SH(6!DM8z+;5&p*FIttxB9 z-Cp?asl*VGY{3Q0YeC-UGUdXftXG&+yJFkk76pD|4|eeAF3&d#k2glkgE&>MF=B1< z(=AB6`6@G+^&GXhz@-ov_z?sY@86GH@S|qOe^jtFY#xKMF;}H= zasNN8TN^;jNYXk#mI;fNDfZ_%l=aoSfJCcT8o#`O`TX@ixmQAbKx3+@Dj1|7Q#;TA z&hm||OZN@EUl?9g_^@o|nf~O!YdmSR|HAKi*ZE0qkJ9Au7hTjUouDYhhd6UlK_$dY zwspi>oeb_Y?2UgQZy7zPx;|S@WP1+xmpJVdq_PK9uRG1r@_c(*HU2_k|8+~jX#WYs zS2-10eyi%o@|I8Ysn@9M-^JD1Vtt?<$B9N=>q4PlON0P5TA@RaEy4r3$aXOBgAe?S zOKY$S>P72L;J8tURhVeXHtr!)HCDv&#%1w&Ayh|~f3)5L5j$Wc7M>UboT%cu_Da2> z)=4lxQO&mWB-gG>41_U8ymULFolgTDYK8YUd(x$2Z@X`a(IORCLWTDhr-7G-J$LTN zdX_*CzVARZNDS^T{}n&D$Mz1CGYcQolnL3u5e3A3VBu^FI{M4$g%IHkG- z6Jyu*UqOsl18`v|I2HU?{-vg*l)7|WkPvjF>g6>x?0o9dN(X>LAV^3+uvG-|I3s=I zh}C(m{s-GpJRip!_GoR=)wm--{8D;|Y)0V03p3LIGj+^TGNXqS#;zTVf|{4RYFWJVt3)zxdKPt#i8 z@)HVL9C#8HW!nD|>@q+opvFxz`>rse$F1jMdvRbjkOTBA^S3c~Wy2Ubg6`wCus5ye zJq}Vsj=ONp4BP_T(L10(hlUKL;Pyrb1~4~J!LxjD_FNoQkBnNI#bDLZ;VkxN7(q9a z3lug7QSkA&6jIaL%4sTLKQ6If7S~X?X~DFx#(;y-Lcb@TXP((Fi14^`mWso&++V#= z(RpbW{r-V;10U_L);*3F;1;ICT1q;&Z7eNrDprj82sQCu)`RIs0FxuEe5 z+z@yrB(ZxNH$m$JY_Q7|3C6=m3vDH%`EPG&IElxu(!C}5Wy%fhJc-1l>}r*H`5uY8 zb2bcwsIYr1-H?L^_iP+TCG={I&O95wD=@WF!AwHt_IZgn z=B=mCj^|>|+u0hvJAUSxLl?zGMW;bf3S{ciL;Si~m&xcj4Vy$FWY~pC_5}8S(*-(h zt9?%w=-|cAEgAdCBb@0 zkT66lm27`;mB2 zz#InO+JS8`@II*MRz107lC&#W50QQ?mJ}C{$b4|CE~y15ii!yhU$kxm$S@!yJ- zzLP8cm4<$6XH!)1!}`X=M_{O6JC>=CM!{TsJ%304^tC>NVxA|HR6(0ME6c6X ztPcoM15aZ1HkSfHzUdcn{W1%SYg-4CYj19X;Km{hJc2sln?Rl2YHDQI+l_j)f|B6f zI}h@Mhe+s``oHYlK19NM%;t^wv;vHYsH?9(A^7w92B6C-gchSy@;s_Nl_WIVrzzJO z1p++DFG@y{&abf+)ECj;p}FrceKgDMT0;>5Qpvk~VKczdy&oz(A?551U?LAy=ZfM@^TK@$ff!o82WbJ)F92 zirNB{KWIC=@~6ER*uTxi_wU^UO}uH_4fBp?b`&&+4%UAM$hEbxsHS)JVhhb|s{D;8 zfVlIx#0>=3;NH^#7BDyLX3ls(8EUiGN|n_51)M^Gw*Z#7SKf*D5j1sY>!)RB+vGMx zt#Ik>&PsI>bX@Ro@J_C77*fSyjzv4)MEjsXQoN+uU(v-4CP~}EJ;30zvb3D*FHER+ z7#beV{;Y2#@E@4h03*7)_a;6wGYDwi8&&S@y0uenrl*d#UcGcLwh3CdQ$5%g27)yp z^qx579LNTZ)8gSli|tTN*6rP`8LAc!^6Z_RO4n=BM(0vSr;VX*w~lzZM;Apx!#kf# z!4^YGo#ov0j$QY|^G?#c&X3?g_ci*Blk*>Vc8A@yZX&914M)&o;pgYzSc3i~3yJ2! z@Y~z};0!RU#a3qe_tItR1eB3Rt!E}s)w0lihFx5v*iv`mi!`tpzYCpxdeGk|N z_6`^uP7Kba*4G#D@`2USuJuIv>i$xFqZSw=1pRm5sBDj9HUiNJ4&T$|(HO40m_G{{ zL}>!Q*1OlOC|Ug#BvXm%^uoX(?u5y;-Y z-cV*0109{9lDe$wpyJ)t!k;vcIJ^i~Ng67o_xAYmkR2Qe($q`%VH(Ee4Oa)UfP4=9 z*AcbEN2an@X>~|H%$C?}`M+Y`UBm8Ox)+z-pXRV^Z~b7lyRj)=jxp-G*-VkA&*tjh za>Tr@KPf3bid@%x0B108mv@wu2`?j-#^Pke>%`IH8%LPc{-7k@s#(PY!A@%?eHrJ2 z7W4N4K7At+-c8l;WuEJTG(e+E({^8DRc~p=(`-E7f8V}1xk<;X==9$igs$*)xYR*a zj?hWRJI7|&F`fZ_^H}lO4>Jx=j6~!E#$Vst_ATuIS5=)~e}%_ae|I@}f)xLjdpzL5 zz2lsRz0Dsz3xcgH=0N#={0JmTXr?y_$k|yoG~PMmq47dAxa%QEQ-T$M?$2<#`rIl z-LB(M`HQ2mFzcB2$_%kEfj18k(H5D|X&-;H{;WyWlKKP2Ta95Q19N>&V7MjtR93%1 z&Uvj*HbK&uqST6##ekC<6OLWV#$6G>ZUg@-3x)rGXCY*Bz0f;jkHKZWWfmlFm3H=s zo*`OA;zZtl*{X52);vmY!@3#dRJA#QmTB;9LhW{7+%EQ`d*bC$`)TO8^-LTcH*-j) zkvr+DAp6QAqOd`c?m1hxrfUIlGf^77nQ(w$$A!miYs@dABZcqeX_tvFjRo30XU`sT zS?!YCTH8reZuv>;c1!avJYS%!aVK!yoo*BcDhrp{(EGf+b+Cm1Q|;)(qqSsI+%N;6 z*WV|i#J;*NDJXz`*RYdP6C(35!Q-bmo$Ej=PDaH!f3NikRN#J5*V&LrMZ%*4<93V7 zhKTYIl;W(3DGkzO-cGGoV2}5tU?i{2jZc`839cnqa?^98pWVOi#&q@c5F?Q^jYvHh|d-cs9j=(gn8!oLHgiRoS2DeW_1PqJ~pyhKkaC@H<`qrHQ z=g$Hu06Q`?;`M7#9fa3ByjFzjm``Q#xXQ)VKo3_1uIt~0?eydWB@dOs?F=ljw$DZ2uE~x zfWtRG(vQo`NRQFuOPw4MV*mDzEFJGU7^zA`@wepb5Fk9j6Ei5G;a;4Oo?d(#N}k7U zLHj5^iAv>j0`X0u&0HN4ciSOG6MYjf|9>uhI2(_H3+F%Nc}bme6%M1k72{5q>td$VG*tDGH{!s( zA;x)?n*(=o?G@tIfP?cg6Z+m7qoewuwO8J$uQ69}!7!cUl#N?_WM|x$p$pA=M0sRb z%Q%Zt=SL^WXLn?fS0A;eBO1Hy|JwEL?(7_4E)=yE~@vRlSA{#k{XLR@Y~5XUpxFjA6=}F&!V>X{Ewdt1K|v1gRm9`QTXuZrTTx zC~1#n^ZrK_aHf}KJqIXA@SPLE^_)cV)k;#+>B^NEdieT%Uc=v<0RG%M?{}0e@k(D^_R#)c z-JR!GQ(L#jUtf<6M6hxY1p|oE6{NSLf=EXpAOfOv2m}ezODtFsIf(QU5$PpTLKFx{ zM@T?QC;@`>mV{nHNOD*3jCL%OCf{ zosZuN39c{6==0_8={!O+y!iF3x<_ge?+nox&EZ z>0jINMqCofEQ#rsWP-f7nc zMv-}!CwvAm)NwKXO!$W#l@=mt3rU>18b>%{2d!Bj+Ud2=e?VwcOHiz2T+}-_W<_4> zWLUFK8etA)L{}L}9-WzhhRSgE!DIDSPJm|!_?%jLx)&zCzBpACrPCqoA&i5%pJp@` zzM&V~jMys8S|_gxMDl8O-++&Fa%hPueM|sD^=bUj*GMI87Y60sUPaGQJwpe zpO%YRwr91j`6NL7-VScxf1j?PJU{kB8;T@%8t8W}%XwGMTzg?5 z5~}H>u%S&cZ@JA_X11r?GiYryn>qDQy23^Ly*su2b+7i6__1jPFg~E(bl4Kj9HSqr zw|(FwWjNu!h_9;skgLI@q{?T+BvU)6*>ACTf1_gtKXq_ zqZhW>dp1ml->+Wj-h9>{7;!87oP~*tG~IKAsb@gAaMgMGQ7O;8 zG>VJknZ9!T%);$ra(n)`TX%Ib9TO6*(RPv#Y>Gu5iP3{G!3*J*o1}PiQar3=7GBKf z%Ce+eT^jL)G_+f9{FR~Vujv~K6O>Yzn=pz(2KCspTctT^39+xmV50VQyIoB{SS`9( zb7kP$oV7SF>issC-%3mG0sZgc_4HQh3;p#u`bqW5@KfdHs-jXST<1N51szjcXyfwha{g%ZoWJoy{O&cJ}J@Pj7`5~TbXD=Z8_eMx>GkJSjJb6zatU>Ueva}?%bzoCH6r*ukJkO8^?Ra zA%o1L#FZ7KHW3b%Jj&J}X7IvRXr1C!KvbZNXHq!ne~x8fp_?Rdr~7txiINq6#YP5c zey5==xUSZ`bWy9#-Fjr__S5jD&q&1E&-Hn4yKqQE1RqVQnZAn3L6j(U;{q^BTHzC; zLw?gNHUvym#FcO3NbW_g+a7E4H%9SqYmy}5Ua_GH>}+gUds6`TySWe7v?j)~e!Mm$ z6x;v64;d@LUGu>MkIqX_rLQCp`}%;^knk?AY1U@yPiH-$eU?{j`Wj?CSNHxCL)WcRk`fD%I4<+!khoX>eio!TnrO<- znm>A90F_*1Y=9m$Pbm9aY56^v?(2nK({7Csc6nHuYZ1k{#fVdMO^42D{)gj3C;YUd z-~$hsh|HGt(M^RSIreI?TE7@O|2S4&+b-eQ-O+glUx@i}&N}OxLr-4&OJOf(dz)`-lrNA+6IVfFW_sXgGFT-z=$V#H-!<&k=); zyNn@6KNGlTYUP{QAD?IyM=Wpn@Rqjde0Is@KC+vg&|1glJIX}nwzJ`C#>Fc9IdHQk z3fPWj+{|O8vh}pibeDTVA&+ueId#}ejppzkNKX-Asi;8FL`{A`sQ z>iWf!TsN*FToKX+S36O*au_7*AkLkFRF>JA0d^eEz zjnVn<;Aor(%HBIIDBe(N!=Gv%_?XO=!ClMRp|O;yiOvJGL4JA#oVny(!$TXK!0?KG zb&ZZ24cp`ry1e4QXbkTEvGHq+AdQA`n`vN@LNDF@Nb|Sms5HAHw)!A|C7~l zN`}j}%A;vzd}{!7o7`LfBE9ON&q%(vq*eZBtP;y)96O5E3feB@s|VpIi|Pxqpg9o!P1e+xS5bNzjGiOawK#nq~>bJb#h zwO32WVjt^VLQ0rws2j+Mk?J#>!-%CptIa&m9uWbz1GE`HsR;`HvRc@uOvx*At^HF; z9vRw0BA#qZkjut#Vz6IyjiICUpidC5pk_97)rH~jnWE`6-!*Z5U88|8?3FFapM*UL=GxSn7KrFntx<0zR^sAjS zN2=Y^sRzf}ylWX>pphG1LkA&+h0=P554GEii;DVAlLRSP#a71(CbtN==8J3aUG=aIj_G70;#nosw~ptP|LY39K$9RXfCGQwy^1)Iq_e zZyQlrSTVtb+cTr5tEdbdiR#(HB>jA7vU@6Fh zE{skTNJ#L?-mBaJfRmwksS6QXYq39Bduwm%2YocIUu~gMJW*=D0M_TEWc37XdYm@> zKEnby=z1mB=P}xacSbCJG3n_-X>{(y2CWA%SmC zYhTUmaoJUE zMfaWFcwi@ywk|=eY?a&!3E*K-%7l5(hI_=zJ2w}8d4ftPDGi<1Gtm$FviO$;DERIx zX0CSD0%Mt(87?@sIhugt;esF#2wA%t1f7IQCM&L>aVW%Swx>0fVH+bA0W8Or81O19 zv$it7e1+`w_w}PMpoaj43)J?K+NVIH0AGHYZ56SB=4LgduAtz;hXWFF0dN@`I)Mo5 zpCP2mCbV-)^Od-E<_IIJFF4rr@Sf%Ym=6FWreEW%^WJXPa?LJ+XYgN)N>$ltZ?#hg zuduoUK5>$w9mK7N0v=I3Ug8LWtlq;bIp2L@Vq#g6qr?0AogtJ|w3wQXKF@}+C=N8ST7=BG%i zdf=rmV+VwR$!?R314G7tXSxjo%g5uDitbJt50qDUMj!Otx{BjX4wJucW|pSL^KAa) zNE%=UKszO+tissm6cx`;)nNKIXveqb9=n%Khbp@Cg5}m1aRn0! zJ90*z@+`jSMl>ohDaktYIBc^f477hrQ}zvJwafXb?J_=E>W8aMqiIJr{Pi-CW51_V z#-R+bHtLAD4^=|J2cJR&f!>ZliQCzj-fkm)c{!bLyy0EiUcqp_fu(1_w*h7|sP7cm z!OX$Ne8WZZ_%$b&d~QlBAbcRde2E9ziyV_bu})2GVCgnSDb7q6+31Ew)WS#K0S<)D zz^|O}0bZMO6`!?-PK`>!-izUT5AY|dW8lQ`hCUf>L5)d3y$UV>gcsJvY6f81To^G; zSAvp03|ZD!tq$x09`_Any>AJcN6HonRs_l;TKZ<^)&HRA8l;h|Ks$WX( z!-=0vNB#VK`fvgMKEMc8&(BeG1;(Y1oxTPtGXcXRevx3>iYQx2GCS)4)Z{>(MezLq z9Y8WX>}e?yGaY&2!}(KW1hdi)@VN@Q+`=_Nx5}BAPXs|vmK(?|EUjspxP*j+UPWF|mB1=1_U;d>x2jtDu)DhsPT+NvK^ShfH6g*XF% zo=H}w-=AxXyY{xr#s)_UQHE=&(t8GfxRdejq|Mr$jT9sCg6S!!&Q8*P4bT$$7od){ z$5ovT$T57m0sg;fBvwCMV~$CRN+FVayej~MR3=$5q)%cUq-_(?;$hqRv*x@F;$&P- z2vYfS$yHosCMG#nh~Ts9WytYC{7hRZt~-L#zA}Zq0*?7|=tmH}Q&9q`2)(hB+Z%rF|&bMG0mPy#}iceAwoCPhOZ)6H6yRQ(mS!Q{} zP1^aes^BQEZwMh8{5cP$$Mo8>5Px1l)$K=~h2(so9IM)-F&|CN0OY|Gdf-wnD`~*D zlDEday+Wa62KcqWOi#fEx<5nsVZH@g@II;k3))Qrx$9})wT z=W)UUXRy9a=tZBL17H;Mizk4$+g8Ojn|763?RayPHIqro$gD9kHVnaDqyI|Rbdm>0 zpbZ+?t2s4@qn)0&&YGR+JvmT{HjM%CN_(y|jgEjko*sTnK}Lsu83yo_Z7jWx(nJ zTPUqj4BL;q_x)%q*c5=fGCWdM0H9F#KKV54FXNYfqlqNfk&9guWP$qmvyA1P;qQC2 z!&8M=>sUknjvm?loAY$yWFF}6wYD0@2JV3JI&OYB0Rep`j9#-9x;@w0717uTzBOam zLEX)7f5d9J_5Ou^$f)qL49Ct#+aLkFg@Dp*Cy|onIAgYSTVs9TWBlh?qQINAW83tq z%|q5I0sc=CL~B$#8q;k+sEB6zP#bPUyN`U*jcw+!cg_K!!8(OJrD~$jkr3Ti0pT~` zq>l6G?TRt0ZG+H)y*N2F$cIu+nh_w0#}7g{KU8B>{!^~`7~2{E>+_J~mmb&r%^3Mh txSx-+qkOk1n1$`Kr#YAXzxfJGV)2%z1fvXZYV1}#^>s~jim%;&`dKYG>A76|0K?{y^*-93m0s>qk#gvdDij8^ z23vmoB0^S`^}*(%KbaFiZsb$8Aq>Kh#W=WjbZ`ji+A%olH)FH3E0I6|cl`gi|EmRl z2MUJXeATi*`||WuVc58%{c2cby-eUHr@fN-Id^`5#($Z4- z9zpHN9<#{}nV;apX=JdEkApunF^&=i67!)3{;IOuT$;q{7z_m89rR4C65QX?Rud## z#+zRxu&&^&?;=gr?@S&AjSk`$3Y~cgjpz?8eOqS~vM1WvwKJczJ^9^`I9$%)`;q{E zqqaX<^Q_dF!1Z-WW$;&Hr98a-VdxAww1>HXl@RebyhkW zdmMjvau@GJFs(~@&ue>97~T_cvN^fzl~=@T>t$3MYWS9gl~qGs)$bK=YwxJyk^DZN zOVL|q!(!D_M~$er=2x=oA8wsan;RbHiU~DA9YOHsu;#h*gbif^#_*VAdAoTPL9kjW zt4se?9kbE-dfosmX!VYkj+IU%+OK4|?1lpo0?})gT`%EyHTrwu(QRbGr~2Re0U^T^ zSdZFT+w0Mg68P+;Z;jKqJr?f|h@sA#-Adi!-90le$B%y_{cJX6uY*6?wHe!<3KU1! zMl`9VrJ-4(By-TLWzugqJ7=Zks<33#367f~RP!WcV+W6U(N@|-Qc;1w3e%sK^FP3Gj;?z>-#f$wp_tC{7 z6*@ISY)tF3=JdL{It$GjI<2>X(wP%>%S5}oqhq6Wt}b4vc#`3tPHPN|E#~UfMywC> z%J9xfu87{dTr_8g!h!@v?>X&`_InIv4y$G=brx-K>1teWN$~KZYtZwmj!GCl9dSD! z$~%d8W{VwP+N?#zyt@BW@vP1vIVGj8b4-8m*H9v_yM>9KPU)kH2dua zR_a$^b^goSd%DrIb|U@Q^Z9cNT4gZ6IGxW{b0=0-RuVX!E-sQiyv4+FCu)6(oenmN zw|6GlxfwNLVh7?I-_mfNogAiipx<9!Y>r%`-)}f#KD<8sOI^~ zuT8CMWO}S&M#u+Y3dT6P8KZUOne$coK58C2uRdnNMg=Ltd@Su9;63ZQun>dO<5Hj* z&+K}&%VqEGFkRrFNy6%q6&E)%*z~0EQ?1rnvMGCgrcAu`di&{~CRd@>KJn$=>{%bN z%0QyAjtS<)6{^yD#=x%HeXP^+*6JLObKcy7mb$vS9cq1zy{}Jz+x_Ab=(%TQpcJjC zCQ*FiyHRJm{fM{_OdKrIy~#>TTM|x(g``Kfw8*9!0LHwf8N~GJBU6>W%Hw z9qVVa&huAR`5~CcN7W-5>^08UcZL=Rps#RRYTd?2=q06;>7d8%4(OAwLGO&pvt4*h zxQI?^Kk+g3I5B;Hd`Adr{n%!ta!r&fCF0M9SDt%|Mv9Y~c9Ygx*P0lCks&{fEv#qVo^$zn?_d3-~mypZOk>I6tc*o)8cgr6y zt80vmjZJLcBY#@sK1Kg1C6!K0#M@f^GaE59d$3kzfcyLBxg9*3?(QomZZ3k@&h!24 zjKe2aXSbL*Qmhxr^UGibw}|Hy!xPlkCs)v2#w~nob9}_>?tSIjyRpuwjVYj2=b}Gf z1I@QkUwTZ-v4E|nhR4Cd@zR%k;IpcF)Ic=HZAS$?IXUam?6pNa7nVRAJX+QLf!v1= z-#3Pm`#nHM3~rvlT7?=ZWmM?2zKo`MhlC6#-Q7-cmM3rdy;?G}Fvv(sdTTqF1Xh`_ z`wDF}wQN_7Ow+ka1W0;%I=hR@)#DI&Wotv}Sb4j{{h6<1Ubvt~lI{#Wh7D~)reF8r3&;y*&!hI2 zCYYujL6>5KeS$?}QO$(ULNXSBV(&dD%ngJAT&w(cHQ( zp`=XIY9b<@oKAuWi-!)dpw=`pDJdy|Uo}?~f@LTi9M7ITGpTtEfk6hSs4zyQmdndn zrYo%KoOWw|R!@$ZPB@?M6eK0Rc177W(`a-!6;~2&>uzrD8qyiK^Kf+hF<5jUe5`2- z-NwXK{qP|f_D!Lxre<52VcvD&vw=Z|)ksE*7b=s*_zrwU-*&ryYg(F}=Kbwv(*Esr zN@@6pk*PcK=Qr`v6U$0REnlFhtV17P!w{bST%|eLA2A){Jm(m>Kc^rgqFgNKxX1)pISn$0|^XkU@@D}+!&PUM4ka$euffMqb|F=ps-C?bSIxSEd=5*8AG z<|Uo!e7~HXlOlgkl+>pa85TxHMj@a04UE3dN)MXT%tH_O6O9U!-M8x>3bh)e`Rwh= zV{{BnOHFp4>;0?8-O&Pg9*|AzI zS%1RU_{i01>{gbMfg!-hd(wGfAeOfh1kem=LQdU_>!`H;|HL{wxh`$J5n zg2ImSlA=E~ruHHtB7}*J_k+Pxb6e#sth{bFit_S5G#@7G&7Zg;Q(yJ2$t?5+!DE`SX0!G!-K!FTx6tM>b1`)%Ck4`$LtPQ( zSAHO-o#v|S&D2_+v{JL+_YL*;L;HKL#vM50d5g5?`2q=Jxfjp3*gTp(mz9*boF9_1 z;tkA=pUt%OC|X~XKGx9xZJwvm;C%0P_oXszMezwwJ!-DXyJ#f793_>y0uIhUk@Uf# zp{BR7eK?GTKaW3kD{0k!nYxJp zy&)esj^xHe@yzh_EKgqze&Q@V0m*AB-QL>hO*m}i8_(yuCg}L`IJc56!JdHJr%`-ixi&s&YvOt9?K16VP^iY?Ng40a)T35(;Tw zTus&tH4QvqLCpL86sweSmD$H&21J!es+i)!6M1bF_)}ai*3A&2;KNaEXS@H-IVEr@ zbcQ^jfI)saO8aaJ0o5UpX0Y}=H@zVc5dUbhF==loI5H=1@2}|BouHdRNFgECoTJBt z50@lvzc(8h99&*mxq5u*j842YSC0JUVVyw5g~lDg=Ve+1qWY*BtE-n!@~8itlHARH zsSs7t8vr%D`{6ExA&{qkefn>l(iq}t8!;5X^$y=6iKHe+r|hq@_yxrg6hg22;%B2V_ zl3F~TFirh+W)g#OF_cUtYl$wkFo8z^!~Jz>1Y0NikSN@%+(va0aQcnVyt9te&VXB( zHOUl)>d6bZ_N~GZQE>JtmNd;@PYB@gU*HN3?jJM-hbi#a<^MN=`sW3bl0);y$^-3Z zX%~mExC~OCzzGeP9E~S9ysoLWcA-SYp=kaKkdudSoh3fqTa)4 z3UzN))|l0Dp3BP03WlMkrlzm2Z%W&Jjz>!Q$EB+ z_x<*Cz4$ZKwM;!Z-w6}xs`C=}aHs*g6`hLp^&VKVQ+2QH^pP!Rj^tDnpw_m8gOvj^ z=x$a`G=?(`(d%wOXT64ChhoHR<Ou(=HG} zgap-wa!)1#djEh*w2&KxLvt8eF^mf?I) z%ipq_>RN(Tneg(iMX<30gu5*h69kY-B!-l)mo(bI+YoE4Iy#tjcC?o5+?3!9mmISv zy6$|=FdPdzhA{LU%S9y>U6>ZAE-owvDltuldPZk=&BCKkzyHJh3MGe(nU;$elI-u{ z%Hv(_=hqvCxSTz0Ps|+@sas~beL{;;v6b;7?9w++(;5#d3lAu$RvxeWnSFn}Zfv}%D4?pScyk2(tf*-FK_@w&!cIdJ9*i)7Uzro-0ET&c zds9z{B$}L}D!2tXoK$(LsdN#JXCmE-lB$M`L*E3#_0}d9aNPhe!3E?M_ip zkpFBz1wi`UyLToN?ZG0cVh%Mm>oY^#Y~;R)A;JhRuwN!tW6^OK*Dc}NwK3+IK~yB# zs#6{F*@n3mG{d1zTw#ef*&WN&ojr~B7hTOe+czzpogdM>Qj-g$@3{2KhW9Rx$hX?I zf9rU_V+m;0yB%{<8C_r);^?oYt*mHNx^@KMi)G%gvkxqJwoVrv3>Fo+eCL-?NcFF; z&&V(cFH9dkTUlLobkejbt4u{R1!-HXpVc>NjF>7Asj~FR}WzC&A?^9`3#Q85g+x&{TG>1t$sK+4~*X#HWSd5fq+ zRLVXwB5QytkaKhT*~!2F+{LQYV7RbOq9Z^}Z4$~SL)TLnpXSBtl%skhH*J1lb66za z<8^Ntx{-WuUPGmWI{HEY-#;TgT{xWE<)#X3mW2sNNJxeAPcP?c`Lf*kXdIpI7X>H! z;{j_R(A-SSeMG=z{S=aBSXlrI8Xg`lJ2|OEC(g>sV!1x6eoI4BW4bP#Lz6S`__4+1 z44cW+sN*32_oRiI%5z+XiOwSLmCRgSB$P>}S&hBw9Y)R>6_2)_+lvD)<~8H7&(!$- z1}0w!5BG?|F(MFt?(@2p-A4$ADH+>;t*%i+2lLh@Ts7}KueOqKGzm#zN**CDNnvWL z0s{dyJJ~zt(#Kj2JJY3^5fKI^>QfyuiRoepwC3ocT{|CNmrv2vjfX$U z95UkD&QEIZe@dpJW?1&5_LFx_OiTmue&=E6vWPzmn+3!DZWbd%&%ahwM`!U)b6QDB z3D9Vp6<<~l1>w7)K~F1Eqs~o zF*4&C^6(ssnMQ7twNj{6@(dSjbT{Uz?;%2-())Air8z$DAY|<9=Msve8;dPbk`$2E zxr)4gldaY*-{e3-Hr`Y}?Bcp>wMmjHXi0H);=zDw1 zSXdDu?R~NJ*9T2Q{mRnP(g{rqn_C%a8Tfc?!8f+hz|8cenWj%i#UHHVxS}F&Cw1tv z4-um!{r$D!pHY%i+Wgtc$}&op{R6K1i3o8vIXfG#W1RpGKf_S({nT_@dt3Qirp4oB z)L53_pq~*$n6{i!0Jb#+g$4&N7EO*0(RY!Rd1dCB8XG&V@6F!S-B`@l(9qDZwX%EL z3@CJzkCDS}ZppN+U_m0OyYuDCt=S`{GqcNBA-$4P9e#v!*@D+#5_`G1O)zSY4iA&T zdJ}l_o#!7VHVUztUhRM9vVyA8Wz2_W;>sirPb^~!JU`u6(O(ARfONjzUc1x>V~P9g z?AK(lX8+*CP3$iMaz%1jT=hYshtDJjhoWajRu+GFXJ;pdfVhtY0t7rzJtA6v zcRvFS%^R4JsZIAOg_1UCaj@A|R;5K@yt&+Tla}r*(2<{rQ^8&D&z}_2M}SCDC}_(t z9?d$V#S*>1GoHyaZYyOG+dxYwVBq7``k^R8NAJVvx0#B!V2flrpEMf>jL#kPS+O< z8s!H20p_;&@fgFFo$dyxt(2{iQV5qfF!AjoRdHKR&A?1rR|1fZe3TS7nK+$KM7x@6 zWL!L9290*GO#vhMGQxW>;xxrh3A^CFKY*}FHOrwvm6Y_!)6q%zl%3!{h$v^IU2NO( ze&>Hpb5G5TvMnY}bqi_gX8!u@Cz`bMI-_QQ%YDO7Z|@LHUy)RQA`XFW%d6w#<9!~N zKlwJ4nhcQ;T7oOKosme!VLoSQVgcGOG<_A7&djucd=%4KM^>(El%JgCe${HoZUO=X zqFyFY_{iMrh2TVPkEUg792}fzNdu!l8jg;R;JG%vVBZ@WzBjmm@YXSqSm->D&-0}U zrT{NtAp>@S@PBpX1hSRj{rOmYf{F9WAKiXs6Ld5*X%?eqsB{!I8w=kJ89$*QA#Tp5 za>YxZ-*qS`3GYPWb8^OsZv$ivIK4kr4qFI1(Us%fg}-RAk?JiR5_v8ohbjeI2ej)s zT~6Nrm7$Ws?vV)If3$VRhe+Mqdt(f@&gLKVLx#NH_uf~lTQyaf6T0rtMuvtnGcoBf z;n-MP^SWH+B^S(%cFHOUF3)-ea!yVC{P}Z0`_9>pS6H~M?8G)%R`qaQvYe6}qLt%> z*rCuIAQ3x@daatz_VL0sX(bUYqV4nByga)3r_Tu3Eel$|;B9R4Ioz}kjYqV70TZ|m z5p8q0_zCo5QOXiviiYJmo(p=4NQjFgp(5)l8`fApG(XHmGEX(DLwR?;KP4P75aVb~ zbEdq*Qa6;jXABV`Fy8|vJ0qq~lkhR)4RItbBcuEJ-4k=P+F|By@eMR0w9Krmjg}6X z+p7}_*nz`L&|sw$3Q8IYrj?7Ei^~u1VkX}n@~AXm_stjTe6ikCd*7So2k%UDT2`ZNp0B1?9VzMA|irF3Yunjnlu0O&&oTS z=U(}zmpzfonf-Yh)j#CcL_L?b0AEuHH7V6O&S*n^`L&E?f2Of8J$Z6G+dm?s9bj;A zaq({Rkh=kG3<1Ax$yayz`aOY(d~P=<)jN;Gz|hZXL|p7y_=*@fRl#x%8AxGw;@I&C1I&o5{=m-arg#Er4!|29hLJf0YA? zQk}4G)kPt7ba{IE{79zG;tTbdn8toJ5%M@L+q!Kn#6V@ep=LUklilm{eT}FXyPd(i zzHN41`@I$9ryx%qSB}6OX4WSscjlVDMkm2}iEU(Jf{)K8CN5quo|`p3G8R&nSBGw8 z{4i5n(<5q|pN69{yqfjSJ~&l?(qbieKXRUqhF`T7a1$S&T0W0GW{KT4W?45j${Ow7 z|7>a}kB0;`iB|bu=x(a7IlC7W96Yr46(n<6SfNL@P{DamtLmCzbVMSe_|dX54SSd4 zqa!6n#TvUUDoRR?(#0m3j|ixUpaToz;^GPd0>Z;rXRpzppKa4pQce_sjXN~H4J$-%^(ejLj&pIVf{dQ0K0A2vf+@AfzysR~I$oe! z0XPgtYZQjZ^V z^q)Qwq2!evWFn)ZVLvO~G4H|_jB|I8ILha4v_X~EifU-eRwEG;J9@@#fMS;tfEsZM zrpI?$8PkXY!DWhDRiYxwly2maiJf8W*RFqqT*-*Ep8T=B7XotKD!E6zE*Q6wpAh(v zpgK+hAH#7o;Hi~*pSR&WR#fR@kVFR$-}s~bt9-OLEo3GK!ZIC}bX zg19liiWpU2ct}hiLkV0BE^7c0LI8Pw5!0Fdc2_A1KvY;zdB=Wa4NFOqQxG6V1KQ#NcV~quG4Fa=HiXgoN%e=hNXrTQ@`xr*`HM3^C57aju*3Majy&K{ zBkQBJ+F-Idk|oseLk|!5%`+Hdi5&(P?jzBkGx>vLX?VwU{maS2mA}Cbyg+n8h4BA0 zdrRJPET`HRaq5EIuA6&MridZ(aL9$5EetM+0TIbp=K47@lCOLB(!*{z{l&C*aM12! zomYIT+b(j?JoEk&%=t{DESO}!kJ?mYU3K{-aqNOEvikidjBdb7C$+Xx=_R)>G?q~G-9xCd@4nhcYQx#O*&2`uHzi2SA0kUHwwbIvj$?B7D)Rj$iu_JsAy@~ zEyqVwgpt+MBvZqMOji&#X`QdAK^ZqEh24Cj*AD>!UteGPzCKDnqLJ>Y95(g$ zI#VY=_P9FTQaXf03j01uX#7*IAL>ybpJjbspxN&CD!^VB45+Jcj5AfY>-B~ZYtd&t zeH8icJy_Tn`p@TFk6+RgnwhK70btb+!a$1dmsotfFREb*XIY=ssMq^_EquAujdU!= z!Se}G_4ao;;$HS|XV^yu5TV4fd{CqvmetvdQ(!E~0DyGMGdtVZ&dx3!ij9rk9S+j4 z8P|g%e-9pyrX=xqr6tQ)m{afnq}sTr`0Wr{Q72{zq^}<1G8VH`zIR-ai3pM3m@W!6 z>@O=h%ep?x&B@x~{d!?TZQrCmV>>e3dl3nJUzaGWZ|goI_~94~@{En7%EQShm*p1N#yhrbYv z1Ko`;^*I-+SS0_>wkF2Mr#{~NgU({(9p~dnf zS&|qlKO1IdWyP6QR#q-3la`Z{tH$IZmieG{ciSQJ(tuvbo7}5qNLGdNKK`A$vAgRf z=IIi2p{U6N5dx$LM=45{;Sh|n}@CN{EwNAg&io_mR#w&)Qc~Bw89X3Bs*M;7Xg7#UN)C^&VFD!C#6yYDbo4IfzDn<1tr&u3%~$c&1>s@5ho60T70dZxw*ONp68;GEXWu#%AjZm9t1f=9dFr- zy=BpBOEsbJ+3wXX10FCx`joyDFI~7O;pn@$)PbJ-?SEwQBfF~ zSSI3dvj!PJdwbt%Bhe{2EYIqkijHmuNbW%12Xxo0avrza27fggri$UI(|O)b-fk?< zpO_f?Jw61NTZ7vizAwv5tx|a8uukSz%o);?_51vl44Tos_k@vJ4Nm?)el%yNdP%^@ zO=sB)wDucZM<#Tm*j+7Z3)Ln^J2!UPBy!oS=IgAZ0i`u!4LVUOM3YgkcKuMfJuzF`L?87BuPST|q#jMYgME(rdqiJw-)NlD1+1F>7`JKADTHE0z412<`~L??psJ*ze7`p7l1`bfEe8_@Jh< z>tYSFjE<7sv{9rf;s3dg7Nuz655CkWiAVOL}{a&ap9lrs&d4 z8L;GE->mX+WL9w^dV7N@U7%Xmnk9xsPM-eZ_PQJzvbCWfSfE)}UM>^QrBd-WU;y0( ztE_WOSVBS}wnc5>-Fmh=zljOW>BcWe|C)gT5)zUiJgT_Nz(+Bu@QhUu_ZpMh?O?JJ zf>IKK`Q0W+K1i@2pz6K!RazV|A&!?;xwHEf81pSGHnvAmkqkx^$)T1QQetgk0TTP( z-kqF@a<*q@OjcI5oU{Deg@>rvH7Q@Z0##)!v=|^lT5=FB%18|BwdS^xK;VS|#7$(i z29#(s_d_|Rz>v8WWmNWi{V^4#7Rqbx~8eu68b5(#Lq*NlaI zi?22|dE5_6va;S&b4+IY>$a_5-H>ldef_Fz$2Nw!g;8HpV!e*Gd#l|*gMq*@ItBtI z3kwYgJNoH}@{ndB0ed>0gh2+yho&Y%wAkF#V_K9aAP;YLWEj;*uMFHTQ8GQ-`y-M$ zg!s1}&&&&HP*Wfs(&Xh!9_FNrz96;{&barmIZ6gy0 ziXw`0b1pHSHGK$Daeh7gwO3uv8o9T4ad88EcoU-oiC1#j+SU>sf%03 zsJS#`1X#18W8xJ(1>)7)XY!u$0hT^hu)HR`XUz4WQwA_&R@GprN>VVh?BoMmRZWgb zb0M9SZvK-_X`(xIjxt#6=@i;HWhr&ovKa=Uqq3q*cRqFu_nM~;`5KqnZu zd2-^wy;ivipy-Q3MDL$%ZB3T*Yin!x_^({9F=pOSk+Co_WfyB1_!pd>dZ`}G2jDU_ zx_-$}Vmq!NOqPub4nMtF%{?dKt0=A{g;5o&KEJv`C+6$w?WJL0kd_uZthaAWTjBG9 zkdMkKDoRSo;3EB$FF?v^J0XxaxH95cideO(2K@w9<-xV0u`!xSIP#N7G%p23MKC-l ziB%#onwPBv1?G=zX*rS1(HijN~}sVM$HBoh`6pA9Tpu=Nd!_it*Qg`rDNus zM^9F_f0vc9;OK`F&(`{)g}h>8ayYx;P!Sas7Ou9UfJwfDkX0rnndy9xdjSy=S%N;B z_x#z2iVU=49U2;7WO{OS@>0{QNZ0GkQmgT>+J;j5wXBLlX+=q%1COm0G>@MCHh~uv zIdE`z6zF<^EV(9m6a!JLMV5&^l%H>i0Tb6d!H8hQqJXvhz!zY-tMOUgl9Q6A_ARDm zq{~rKtZr_3HlQwB(~2R4hllO#ngPS7qN;2X$dir!kWo`dV}L{}G$z7sJHS#)tHH-d zd_tONbtUJVLB)9b0ueD8lKkZA6j1CVV?)IyC3VjCO~2h-P3@Z6Ih9M1M@q}fJW)2> zw@xODIzNJRL|fak4U_$P9LS8ck&HZN6NoC6d zFzZ0KSb~L9D5x5`GP12MA%VPAcf1-F78b|h-qbZD*E}HC?D0gniOyN$9Zi3It$nH+ zM6fO2SRpmy-sBkjdJA16ge+e#22 zk{JTbsRqm$QrSJGF%nfi=jXl`7?ta=LjKnmG4|B7DpwjGy5kOKHCWac&C7TLl>|ED zGa*WV!-x&V5m3){1q%NG7L!^?1O8nB@Y8tXA^G}fJ%4^RSI-6V@~T7}Rwtbo!C+L1 zc$Qn&Ljl7I*yp(FnJdQ_XHJDf+?~T0g7KQ`y+DiLa=7SBN$+8d=SVLo7=N#&E+*z} zBp4Bd`bFMCP;MD{1dsr~3m4!e@$@^pCMzw7UJ?gKg<&7=)rUp;6!@F^syIEm;yjp` z&wPa<(qU(Ax@r54qA%7xz{lzQ&b5t%>MjV`MPjUnUJ?;v@CiiP)oDhW?Ans&&l`8n zTE4t9ytSN4r@e>OxlSi*PFsa$RG2TnBE-i(WU$_B7Or4x^sY6j2u*E?4O&3o=Q}z% zd0EqIY^*QVs?A!vV*<>%u%Hr^Lk2E=#Toqn+X(xM2O{pfR_7R+#&h84<0qXtU~7eAKV~MwSSYnU5gqeNyWuJ@cRB=^bhERw#i~r~Cdb61-l=cm ze_6h$MJJqTw5iR`?z%oFv6!!>g+PR29=MkSiOWZ#We=VA^!YnIec{NjSxxCFR_0b+ zF?e{?#i~uo$-__c4R9|!@jLr^rrj?P6h1T!H^>V;8XAzmf*VESrCRSCt2yPD@V;x?@HCx+c))^fi)F9S(Gb;^R%hUYn z4BjF+H70C@z0u6j>Y20^Y^#Z*3~2>2_57zxrSPa5?gyPiKw>$la++o{S5#0SmDgs~XjZDD)%5U(_So;w4`}a1gvFU8Wmf8( zic?^D{oN)(`_*rG{-zTB3j^DwVmJ*(542`$Nf*AIhia!iR0_Dg>B7y(B;|j26p#*6 zko`q#flpLwfMpA~b=`UU?#EYEy%+cu7nfI4;~DSoUl4GH{?Yh>G-^8hhcvoV zd4>4D%N3kunErziO5TY2IlGZer(@3ydq8dCWi_`mT$(A+f zvcXENJx&jhpaOx5nu?15S5kmlRIF6=Tes1JMfDi`2K#*=v9&%m`FyuT?t|n*8F+I! zkbayhg2LsvhVtONk|p>GY)Lf9L5Cm1-Kj#kq>{#ypZh+bFpWwya-2`=VauS!j5QStZ zFE35euG6SHjdd;gEc@v|7Hl>2I~MpXfboY! zkYzKVdHK-Ee&4f*pGbGbQ{mn;0z(=5^3JugKc2&k9^cd8|KLd9L_H?M^EqVY)+?}PYN{K@I@v3bMCDUN&sd*RRD5V`ZtE9-r*mk! zZ!R2wEv4$h0SGA8ldu2HXTkigySEp~;j+YXAOUbD*~Z2se6A637?=VnDJivfducf- zS9GrzA_mM;Go<3-QLzz_3Y?wY7p?tKVt+X^2y_A>3b-g11--Ce;>@~Vd^|!&_4n0WKl+`lj2p@E%~`pSD?80Gg6tEKql64$g{=_49L6 z1rgMi_vBPntK28-l$6F&J7j9|RUK~zALWq#XW;UUeV0>^kx|&eWqq?!B}eM-AKSxx z$73H1V+Hn${}1*>L-XI5mQb%MBNv&!^%4;8NaRBIviS5o4ixmZ{d!iUd&cop3T~i6 ziUQ&W;l8*paq1CJ^g6rcMI8_zfQ2sOFvItc09IOZnjAq5N?>yX^h&+?_Rh`@pe&wy z47Kv#&w8ON-rAG!#YDY|)X0bF14?cA;-)zz<;2e~LRs0$g%<)F2wbsbT|F|ny!vPMtU|iC8g<1otlnSacX=%w?}eLO7F;+ z6kr(+J^`zax3`!%At4}!6~n@J8;I^O&mT8t(*6e}B37CJh}gcqUT};j8h%(pLU2e- zPF~(4QeU7-9ISlRDS)y|Jb+W=!vu5}+!(l9ZxZWFjQiyge>LMedo>2Y;#x;-i)K(KUi z1FR0dpELoeDCdX+>}0|fe45+#xX<4cg+=zgz@N7WPk$H=Bz7DwBSNmY6H+}~CkuP2 z0h9#P3S=qh4vD2_*b%Yx0-dra94}5(peNi%afD195~ydI2@F&Tikw*cigI$2&*>z0p2Ejo{24{X!@~pF3)qHcmqNZE z4ur=>Qc_apzGjpedqIQ{e(>1+DoA!hHr3)Ejss)3QKnF7duCnoem*uRXlEy`e|H*7 z;Ns?~z{f^NOE&{?B8E(T*M^YZFsW*a1h$G3UC7UU=-O(~c`PP4clPw`Gl>O9_~{h%XvvF-p6*3W9Sbjn3+ zeGcr?yDGpzyCIpE1t5{f?_I!Suz}k{KuH_ zK%z`5I1-d`?1Y4bfYKNmk9310z?wK1f`n1zxq`@c>fh95(p^eQYH3L+P-)@`iGbhA zx6NdE0|Sd7HZoD$E;ui52iBTq7Z3mlL>iiIyL|I3>n`R&q0$Qs8!Ac&xk4DKRyFPg z=bNpytH~)#ee{2_I%*?qUrQslNJDW&!mj<_>=%H;fdP z*T&S9y;`VgXs!+pu>x}?K+jh}k}Vx;!U@r67^tb~>BDv3>KT=#rKJrPz0Khc7;wL& zc}q*HL4}PZA>j*58xoD|eoF}w!otxli`>Wv7zj_*CMOxS8gh=FjYoxxh0@%cG$1Gc zA2kIHcPKKV8nr-_C?}2u>esW!9~^{QmvCs2k!d+R!J>9}xSZ+3_?ClQziD`t@$r+= z2aBFfp_8Vs%*Ud)?4bsI|7ormkQ)Mw(*38#yOm95MDu?yr%=2s1vSv$p2LeGY^Pdx zs4ih)Zrauu$>Tv|YG-CXtyOk8tZbvOfC8VNCD4ybf3-1Ox4uV5!Bsf@3v#cp1DQi* zPgXv~;bOsS_QH7}5QQ>gWaVb7X{dE~cFLmT5fC&YBFZT!=$aZUnW};LY>r0BN-rg- z8IRFCG6opD_EqH&3_uG`PEXFcUt|>}@hd2d3>JyW%fEvNiHd@QcgKn5>*`#9Qc)1k z+1=&3&y`lS{sv~d`=@DtvlKa!fmw|X3u}2L413EEiuprNAFgPJ<|GrCkW`h3Wft3*y$ zHoAB6s&7UoCNzp$`x>C8)Dv_C{ZlwSXC$2OER!|d+}tAj<3AbLg%3U#=)4-E1%&|Y zPD|MM{%b~t>zsxZa{z-S9cC{L4(1vL_Nq_4mgEz1_?ko&rEeLjaM(Qo#se;ca{wV* zSob_2`5Clt4Nrb8N(u`PZC;d?lrTCt1rTxk=tjImL}a(T^dn*|sj9lTN$&~o=RZEF z83&=oaU^3SX6sCj^yMLhL4_(ZMBz0RU5KpIcSvSB*+2?zxlw;h(WM(xaq&6HtF5aVX>!nmZ#jo!#6{lt0( zs7+4)_OazvJ$)YcOC&=mxZ}6P$A~&DgXy}W_qdGQg?EBREIbSoLro3CoFHClRoO;! z_83`Q1X7NExa;ZN`?|5Er6)R~u# z`r{k#n54qBRoCL!V09@TF^w#s_v_mS!sb`{6IW{aNV(m|{gnAz_9oF><3z`HcTG60wT(4MdOa<&BxG)%0D03$U&rdIm8q}?R|JKq1iHSLN-6#3 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 KPyoC2@V@|SR1psV