blob: 963afec876c1bb56d84724a064314392eabeb327 (
plain) (
blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
|
using ArcaneLibs;
using ArcaneLibs.Extensions;
namespace Rhea.Nar;
/// <summary>Serialization class for uncompressed NAR streams</summary>
public class NarWriter
{
/// <summary>Serialises a given path to a NAR stream</summary>
/// <param name="path"></param>
/// <returns>An enumerator of chunks of the stream, in order</returns>
public IEnumerable<byte[]> GetNarStreamContents(string path)
{
yield return NarConsts.Header;
foreach (var chunk in GetNarStreamContentsInner(path)) yield return chunk;
}
private IEnumerable<byte[]> 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<byte[]> 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<byte[]> 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<byte[]> 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<byte> 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
}
|