using ArcaneLibs; using ArcaneLibs.Extensions; namespace Rhea.Nar; /// Serialization class for uncompressed NAR streams public class NarWriter { /// Serialises a given path to a NAR stream /// /// An enumerator of chunks of the stream, in order public IEnumerable GetNarStreamContents(string path) { yield return NarConsts.Header; foreach (var chunk in GetNarStreamContentsInner(path)) yield return chunk; } private IEnumerable GetNarStreamContentsInner(string path) { FileInfo fe = new FileInfo(path); if (string.IsNullOrWhiteSpace(fe.LinkTarget)) { if (fe.Attributes.HasFlag(FileAttributes.Directory)) foreach (var chunk in GetNarDirectoryContents(new DirectoryInfo(path))) yield return chunk; else foreach (var chunk in GetNarFileContents(fe)) yield return chunk; } else foreach (var chunk in GetNarSymlinkContents(fe)) yield return chunk; } private IEnumerable GetNarStreamContentsInner(FileSystemInfo fsi) { if (string.IsNullOrWhiteSpace(fsi.LinkTarget)) { if (fsi is DirectoryInfo di) foreach (var chunk in GetNarDirectoryContents(di)) yield return chunk; else foreach (var chunk in GetNarFileContents(fsi as FileInfo ?? throw new Exception(fsi.GetType().Name + " != FileInfo"))) yield return chunk; } else yield return GetNarSymlinkContents(fsi); } private IEnumerable GetNarDirectoryContents(DirectoryInfo de) { yield return NarConsts.LeftParen; yield return CombinedNarConsts.TypeDirectory; foreach (var fe in FileUtils.EnumerateDirectory(de.FullName).OrderBy(x => x.Name, StringComparer.Ordinal)) { yield return CombinedNarConsts.EntryWithName; yield return GetLengthPrefixedPadded(fe.Name.AsBytes().ToArray()); yield return NarConsts.Node; foreach (var chunk in GetNarStreamContentsInner(fe)) yield return chunk; yield return NarConsts.RightParen; } yield return NarConsts.RightParen; } private byte[] GetNarSymlinkContents(FileSystemInfo fe) { return [ ..NarConsts.LeftParen, ..CombinedNarConsts.SymlinkWithTarget, ..GetLengthPrefixedPadded(fe.LinkTarget!.AsBytes().ToArray()), ..NarConsts.RightParen ]; } private IEnumerable GetNarFileContents(FileInfo fe) { yield return NarConsts.LeftParen; yield return CombinedNarConsts.TypeRegular; if ( fe.UnixFileMode.HasFlag(UnixFileMode.UserExecute) || fe.UnixFileMode.HasFlag(UnixFileMode.GroupExecute) || fe.UnixFileMode.HasFlag(UnixFileMode.OtherExecute) ) { yield return CombinedNarConsts.ExecutableWithPadding; } yield return NarConsts.Contents; yield return BitConverter.GetBytes((ulong)fe.Length); using (var fs = File.OpenRead(fe.FullName)) { int chunkSize = ushort.MaxValue; 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 targetLength = Math.Ceiling(fe.Length / 8d) * 8UL; var targetLength = ((fe.Length + 7) / 8) * 8; var padSize = (int)(targetLength - fe.Length); yield return new byte[padSize]; yield return NarConsts.RightParen; } #region Utility methods internal static byte[] GetLengthPrefixedPadded(ReadOnlySpan data) { byte[] unpadded = [..BitConverter.GetBytes((ulong)data.Length), .. data]; // var targetLength = Math.Ceiling(unpadded.Length / 8d) * 8; // Array.Resize(ref unpadded, (int)targetLength); var targetLength = ((unpadded.Length + 7) / 8) * 8; Array.Resize(ref unpadded, targetLength); return unpadded; } #endregion }