4internal static class NdsHostDirectoryImporter
12 public static async ValueTask<NdsHostDirectorySnapshot> StageAsync(
13 string sourceDirectory,
14 string destinationDirectory,
15 NdsDirectoryImportOptions options,
16 CancellationToken cancellationToken)
18 string sourceRoot = Path.GetFullPath(sourceDirectory);
19 if (!Directory.Exists(sourceRoot))
21 throw new DirectoryNotFoundException($
"Host import directory was not found: {sourceRoot}");
24 if ((File.GetAttributes(sourceRoot) & FileAttributes.ReparsePoint) != 0)
26 throw new IOException(
"The host import root cannot itself be a link or reparse point.");
29 var directories =
new List<string> { destinationDirectory };
30 var files =
new List<NdsHostFileSnapshot>();
31 var pending =
new Stack<(string Host, string Relative)>();
32 pending.Push((sourceRoot,
string.Empty));
35 while (pending.Count != 0)
37 cancellationToken.ThrowIfCancellationRequested();
38 (
string hostDirectory,
string relativeDirectory) = pending.Pop();
39 string[] entries = Directory.EnumerateFileSystemEntries(hostDirectory)
40 .Order(StringComparer.Ordinal)
42 for (
int index = entries.Length - 1; index >= 0; index--)
44 string entry = entries[index];
45 FileAttributes attributes = File.GetAttributes(entry);
46 if ((attributes & FileAttributes.ReparsePoint) != 0)
50 throw new IOException($
"Host import encountered a link or reparse point: {entry}");
57 string relative =
string.IsNullOrEmpty(relativeDirectory)
58 ? Path.GetFileName(entry)
59 : relativeDirectory +
"/" + Path.GetFileName(entry);
60 string target = Combine(destinationDirectory, relative);
61 if ((attributes & FileAttributes.Directory) != 0)
63 directories.Add(target);
64 pending.Push((entry, relative));
68 if (files.Count >= options.MaximumFiles)
70 throw new InvalidDataException(
"Host directory import exceeds the configured file-count limit.");
72 long length =
new FileInfo(entry).Length;
73 if (length > options.MaximumTotalBytes - totalBytes || length > Array.MaxLength)
75 throw new InvalidDataException(
"Host directory import exceeds the configured byte allocation limit.");
78 byte[] contents = await File.ReadAllBytesAsync(entry, cancellationToken).ConfigureAwait(
false);
79 if (contents.LongLength > options.MaximumTotalBytes - totalBytes)
81 throw new InvalidDataException(
"A host file changed size and exceeded the import allocation limit while being read.");
84 totalBytes = checked(totalBytes + contents.LongLength);
85 files.Add(
new(target, contents));
90 directories.Distinct(StringComparer.Ordinal).Order(StringComparer.Ordinal).ToArray(),
91 files.OrderBy(
static value => value.Path, StringComparer.Ordinal).ToArray(),
97 private static string Combine(
string destinationDirectory,
string relativePath) =>
98 NdsFileSystemBuilder.NormalizePath(
99 destinationDirectory ==
"/" ?
"/" + relativePath : destinationDirectory +
"/" + relativePath,
106internal sealed record NdsHostFileSnapshot(
string Path,
byte[] Contents);
113internal sealed record NdsHostDirectorySnapshot(
114 string[] Directories,
115 NdsHostFileSnapshot[] Files,
NdsHostLinkPolicy
Controls treatment of host reparse points and symbolic links during bounded directory ingestion.