Working files and symlinks?
2 files changed, 163 insertions, 0 deletions
diff --git a/Rhea.Nar/NarWriter.cs b/Rhea.Nar/NarWriter.cs
new file mode 100644
index 0000000..a01cd3d
--- /dev/null
+++ b/Rhea.Nar/NarWriter.cs
@@ -0,0 +1,149 @@
+using System.IO.Enumeration;
+using System.Runtime.InteropServices;
+using ArcaneLibs;
+using ArcaneLibs.Extensions;
+
+namespace Rhea.Nar;
+
+public ref struct NarConsts
+{
+ public static readonly byte[] Header = NarWriter.GetLengthPrefixedPadded("nix-archive-1"u8);
+
+ public static readonly byte[] LeftParen = NarWriter.GetLengthPrefixedPadded("("u8);
+ public static readonly byte[] RightParen = NarWriter.GetLengthPrefixedPadded(")"u8);
+
+ public static readonly byte[] Regular = NarWriter.GetLengthPrefixedPadded("regular"u8);
+ public static readonly byte[] Directory = NarWriter.GetLengthPrefixedPadded("directory"u8);
+ public static readonly byte[] Symlink = NarWriter.GetLengthPrefixedPadded("symlink"u8);
+
+ public static readonly byte[] Entry = NarWriter.GetLengthPrefixedPadded("entry"u8);
+ public static readonly byte[] Name = NarWriter.GetLengthPrefixedPadded("name"u8);
+ public static readonly byte[] Target = NarWriter.GetLengthPrefixedPadded("target"u8);
+ public static readonly byte[] Node = NarWriter.GetLengthPrefixedPadded("node"u8);
+ public static readonly byte[] Type = NarWriter.GetLengthPrefixedPadded("type"u8);
+ public static readonly byte[] Executable = NarWriter.GetLengthPrefixedPadded("executable"u8);
+
+ public static readonly byte[] Contents = NarWriter.GetLengthPrefixedPadded("contents"u8);
+}
+
+public class NarWriter
+{
+ public Span<byte> GetHeader()
+ {
+ return NarConsts.Header;
+ }
+
+ public IEnumerable<byte[]> GetNarStreamContents(string storePath)
+ {
+ yield return NarConsts.Header;
+ FileAttributes fattr = File.GetAttributes(storePath);
+ if (fattr.HasFlag(FileAttributes.Directory))
+ yield return NarConsts.Directory;
+ else
+ {
+ var linkTarget = new FileInfo(storePath).LinkTarget;
+ if (string.IsNullOrWhiteSpace(linkTarget))
+ foreach (var chunk in GetNarFileContents(new FileInfo(storePath)))
+ yield return chunk;
+
+ else
+ foreach (var chunk in GetNarSymlinkContents(new FileInfo(storePath)))
+ yield return chunk;
+ }
+ // yield return NarConsts.LeftParen;
+ // yield return NarConsts.Type;
+ // FileAttributes fattr = File.GetAttributes(storePath);
+ // if (fattr.HasFlag(FileAttributes.Directory))
+ // yield return NarConsts.Directory;
+ // else
+ // {
+ // var linkTarget = new FileInfo(storePath).LinkTarget;
+ // if (string.IsNullOrWhiteSpace(linkTarget))
+ // yield return NarConsts.Regular;
+ // else yield return NarConsts.Symlink;
+ // }
+ //
+ // foreach (var fe in FileUtils.EnumerateDirectory(storePath).OrderBy(x=>x.Name))
+ // {
+ // yield return NarConsts.Entry;
+ // yield return NarConsts.LeftParen;
+ //
+ // yield return NarConsts.Name;
+ // yield return GetLengthPrefixedPadded(fe.Name.AsBytes().ToArray());
+ //
+ // yield return NarConsts.Type;
+ // if (fe.LinkTarget is not null)
+ // {
+ // yield return NarConsts.Symlink;
+ // yield return NarConsts.Target;
+ // yield return GetLengthPrefixedPadded(fe.LinkTarget.AsBytes().ToArray());
+ // }
+ //
+ // yield return NarConsts.RightParen;
+ // }
+ }
+
+ private IEnumerable<byte[]> GetNarSymlinkContents(FileInfo fe)
+ {
+ yield return NarConsts.LeftParen;
+
+ yield return NarConsts.Type;
+ yield return NarConsts.Symlink;
+ yield return NarConsts.Target;
+ yield return GetLengthPrefixedPadded(fe.LinkTarget!.AsBytes().ToArray());
+
+ yield return NarConsts.RightParen;
+ }
+
+ private IEnumerable<byte[]> GetNarFileContents(FileInfo fe)
+ {
+ yield return NarConsts.LeftParen;
+
+ yield return NarConsts.Type;
+ yield return NarConsts.Regular;
+ if (
+ fe.UnixFileMode.HasFlag(UnixFileMode.UserExecute)
+ || fe.UnixFileMode.HasFlag(UnixFileMode.GroupExecute)
+ || fe.UnixFileMode.HasFlag(UnixFileMode.OtherExecute)
+ )
+ yield return NarConsts.Executable;
+
+ var targetLength = Math.Ceiling(fe.Length / 8d) * 8UL;
+ yield return NarConsts.Contents;
+ yield return BitConverter.GetBytes((ulong)fe.Length);
+
+ using (var fs = File.OpenRead(fe.FullName))
+ {
+ int chunkSize = 32;
+ long remaining = fs.Length;
+ byte[] buf = new byte[chunkSize];
+ while (remaining > 0)
+ {
+ int read;
+ remaining -= read = fs.Read(buf, 0, chunkSize);
+ yield return buf[..read];
+ }
+ }
+
+ var padSize = (int)(targetLength - fe.Length);
+ yield return new byte[padSize];
+
+ yield return NarConsts.RightParen;
+ }
+
+ #region Utility methods
+
+ internal static byte[] GetLengthPrefixedPadded(ReadOnlySpan<byte> data)
+ {
+ byte[] unpadded = [..BitConverter.GetBytes((ulong)data.Length), .. data];
+ var targetLength = Math.Ceiling(unpadded.Length / 8d) * 8;
+ // Console.Write($"{unpadded.Length:X2} - ");
+ // unpadded.HexDump();
+ Array.Resize(ref unpadded, (int)targetLength);
+ // Console.Write($"{unpadded.Length:X2} - ");
+ // unpadded.HexDump();
+ return unpadded;
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/Rhea.Nar/Rhea.Nar.csproj b/Rhea.Nar/Rhea.Nar.csproj
new file mode 100644
index 0000000..bcfc328
--- /dev/null
+++ b/Rhea.Nar/Rhea.Nar.csproj
@@ -0,0 +1,14 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net10.0</TargetFramework>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <Nullable>enable</Nullable>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="ArcaneLibs" Version="1.0.1-preview.20260812-173005" />
+ </ItemGroup>
+
+</Project>
|