Skip to content

Commit 5d047db

Browse files
authored
Merge pull request microsoft#73 from yerudako/user/yerudako/AddPrjInfo2Wrapper
Enable symlinks in ProjFS-Managed
2 parents b6fbd6b + 12a50c8 commit 5d047db

16 files changed

+1184
-83
lines changed

ProjectedFSLib.Managed.API/ApiHelper.cpp

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ using namespace System::IO;
1111
using namespace Microsoft::Windows::ProjFS;
1212

1313
ApiHelper::ApiHelper() :
14-
useRS5Api(false)
14+
supportedApi(ApiLevel::v1803)
1515
{
1616
auto projFsLib = ::LoadLibraryW(L"ProjectedFSLib.dll");
1717
if (!projFsLib)
@@ -22,6 +22,7 @@ ApiHelper::ApiHelper() :
2222
if (::GetProcAddress(projFsLib, "PrjStartVirtualizing") != nullptr)
2323
{
2424
// We have the API introduced in Windows 10 version 1809.
25+
this->supportedApi = ApiLevel::v1809;
2526

2627
this->_PrjStartVirtualizing = reinterpret_cast<t_PrjStartVirtualizing>(::GetProcAddress(projFsLib,
2728
"PrjStartVirtualizing"));
@@ -49,6 +50,16 @@ ApiHelper::ApiHelper() :
4950

5051
this->_PrjMarkDirectoryAsPlaceholder = reinterpret_cast<t_PrjMarkDirectoryAsPlaceholder>(::GetProcAddress(projFsLib,
5152
"PrjMarkDirectoryAsPlaceholder"));
53+
if (::GetProcAddress(projFsLib, "PrjWritePlaceholderInfo2") != nullptr)
54+
{
55+
// We have the API introduced in Windows 10 version 2004.
56+
this->supportedApi = ApiLevel::v2004;
57+
58+
this->_PrjWritePlaceholderInfo2 = reinterpret_cast<t_PrjWritePlaceholderInfo2>(::GetProcAddress(projFsLib,
59+
"PrjWritePlaceholderInfo2"));
60+
61+
this->_PrjFillDirEntryBuffer2 = reinterpret_cast<t_PrjFillDirEntryBuffer2>(::GetProcAddress(projFsLib, "PrjFillDirEntryBuffer2"));
62+
}
5263

5364
::FreeLibrary(projFsLib);
5465

@@ -66,7 +77,15 @@ ApiHelper::ApiHelper() :
6677
"Could not get a required entry point."));
6778
}
6879

69-
this->useRS5Api = true;
80+
if (this->supportedApi >= ApiLevel::v2004)
81+
{
82+
if (!this->_PrjWritePlaceholderInfo2 ||
83+
!this->_PrjFillDirEntryBuffer2)
84+
{
85+
throw gcnew EntryPointNotFoundException(String::Format(CultureInfo::InvariantCulture,
86+
"Could not get a required entry point."));
87+
}
88+
}
7089
}
7190
else if (::GetProcAddress(projFsLib, "PrjStartVirtualizationInstance") == nullptr)
7291
{
@@ -127,7 +146,12 @@ ApiHelper::ApiHelper() :
127146
}
128147
}
129148

130-
bool ApiHelper::UseRS5Api::get(void)
149+
bool ApiHelper::UseBetaApi::get(void)
150+
{
151+
return (this->supportedApi == ApiLevel::v1803);
152+
}
153+
154+
ApiLevel ApiHelper::SupportedApi::get(void)
131155
{
132-
return this->useRS5Api;
156+
return this->supportedApi;
133157
}

ProjectedFSLib.Managed.API/ApiHelper.h

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,20 @@
66
namespace Microsoft {
77
namespace Windows {
88
namespace ProjFS {
9+
/// <summary>
10+
/// Defines values describing the APIs available from this wrapper.
11+
/// </summary>
12+
enum class ApiLevel : short
13+
{
14+
/// <summary>Using Windows 10 version 1803 beta API.</summary>
15+
v1803 = 1803,
16+
17+
/// <summary>Using Windows 10 version 1809 API.</summary>
18+
v1809 = 1809,
19+
20+
/// <summary>Adding APIs introduced in Windows 10 version 2004.</summary>
21+
v2004 = 2004,
22+
};
923
/// <summary>Helper class for using the correct native APIs in the managed layer.</summary>
1024
/// <remarks>
1125
/// <para>
@@ -32,13 +46,37 @@ ref class ApiHelper {
3246
internal:
3347
ApiHelper();
3448

35-
property bool UseRS5Api
49+
property bool UseBetaApi
3650
{
3751
bool get(void);
38-
};
52+
}
53+
54+
property ApiLevel SupportedApi
55+
{
56+
ApiLevel get(void);
57+
}
3958

4059
private:
4160

61+
#pragma region Signatures for Windows 10 version 2004 APIs
62+
63+
typedef HRESULT(__stdcall* t_PrjWritePlaceholderInfo2)(
64+
_In_ PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT namespaceVirtualizationContext,
65+
_In_ PCWSTR destinationFileName,
66+
_In_reads_bytes_(placeholderInfoSize) const PRJ_PLACEHOLDER_INFO* placeholderInfo,
67+
_In_ UINT32 placeholderInfoSize,
68+
_In_opt_ const PRJ_EXTENDED_INFO* ExtendedInfo
69+
);
70+
71+
typedef HRESULT(__stdcall* t_PrjFillDirEntryBuffer2) (
72+
_In_ PRJ_DIR_ENTRY_BUFFER_HANDLE dirEntryBufferHandle,
73+
_In_ PCWSTR fileName,
74+
_In_opt_ PRJ_FILE_BASIC_INFO* fileBasicInfo,
75+
_In_opt_ PRJ_EXTENDED_INFO* extendedInfo
76+
);
77+
78+
#pragma endregion
79+
4280
#pragma region Signatures for Windows 10 version 1809 APIs
4381

4482
typedef HRESULT (__stdcall* t_PrjStartVirtualizing)(
@@ -169,10 +207,14 @@ ref class ApiHelper {
169207

170208
#pragma endregion
171209

172-
bool useRS5Api;
210+
ApiLevel supportedApi{ ApiLevel::v1803 };
173211

174212
internal:
175213

214+
// 2004 API
215+
t_PrjWritePlaceholderInfo2 _PrjWritePlaceholderInfo2 = nullptr;
216+
t_PrjFillDirEntryBuffer2 _PrjFillDirEntryBuffer2 = nullptr;
217+
176218
// 1809 API
177219
t_PrjStartVirtualizing _PrjStartVirtualizing = nullptr;
178220
t_PrjStopVirtualizing _PrjStopVirtualizing = nullptr;

ProjectedFSLib.Managed.API/DirectoryEnumerationResults.h

Lines changed: 134 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
#pragma once
55

66
#include "IDirectoryEnumerationResults.h"
7+
#include "ApiHelper.h"
8+
9+
using namespace System;
10+
using namespace System::Globalization;
11+
using namespace System::IO;
712

813
namespace Microsoft {
914
namespace Windows {
@@ -18,10 +23,9 @@ namespace ProjFS {
1823
public ref class DirectoryEnumerationResults : public IDirectoryEnumerationResults {
1924
internal:
2025

21-
DirectoryEnumerationResults(PRJ_DIR_ENTRY_BUFFER_HANDLE bufferHandle)
22-
{
23-
m_dirEntryBufferHandle = bufferHandle;
24-
}
26+
DirectoryEnumerationResults(PRJ_DIR_ENTRY_BUFFER_HANDLE bufferHandle, ApiHelper^ apiHelper) :
27+
m_dirEntryBufferHandle(bufferHandle), m_apiHelper(apiHelper)
28+
{ }
2529

2630
// Provides access to the native handle to the directory entry buffer.
2731
// Used internally by VirtualizationInstance::CompleteCommand(int, IDirectoryEnumerationResults^).
@@ -139,14 +143,137 @@ public ref class DirectoryEnumerationResults : public IDirectoryEnumerationResul
139143
System::DateTime lastAccessTime,
140144
System::DateTime lastWriteTime,
141145
System::DateTime changeTime) sealed
146+
{
147+
ValidateFileName(fileName);
148+
149+
pin_ptr<const WCHAR> pFileName = PtrToStringChars(fileName);
150+
PRJ_FILE_BASIC_INFO basicInfo = BuildFileBasicInfo(fileSize,
151+
isDirectory,
152+
fileAttributes,
153+
creationTime,
154+
lastAccessTime,
155+
lastWriteTime,
156+
changeTime);
157+
158+
auto hr = ::PrjFillDirEntryBuffer(pFileName,
159+
&basicInfo,
160+
m_dirEntryBufferHandle);
161+
162+
if FAILED(hr)
163+
{
164+
return false;
165+
}
166+
167+
return true;
168+
}
169+
170+
/// <summary>Adds one entry to a directory enumeration result.</summary>
171+
/// <remarks>
172+
/// <para>
173+
/// In its implementation of a <c>GetDirectoryEnumerationCallback</c> delegate the provider
174+
/// calls this method for each matching file or directory in the enumeration.
175+
/// </para>
176+
/// <para>
177+
/// If this method returns <c>false</c>, the provider returns <see cref="HResult::Ok"/> and waits for
178+
/// the next <c>GetDirectoryEnumerationCallback</c>. Then it resumes filling the enumeration with
179+
/// the entry it was trying to add when it got <c>false</c>.
180+
/// </para>
181+
/// <para>
182+
/// If the method returns <c>false</c> for the first file or directory in the enumeration, the
183+
/// provider returns <see cref="HResult::InsufficientBuffer"/> from the <c>GetDirectoryEnumerationCallback</c>
184+
/// method.
185+
/// </para>
186+
/// </remarks>
187+
/// <param name="fileName">The name of the file or directory.</param>
188+
/// <param name="fileSize">The size of the file.</param>
189+
/// <param name="isDirectory"><c>true</c> if this item is a directory, <c>false</c> if it is a file.</param>
190+
/// <param name="fileAttributes">The file attributes.</param>
191+
/// <param name="creationTime">The time the file was created.</param>
192+
/// <param name="lastAccessTime">The time the file was last accessed.</param>
193+
/// <param name="lastWriteTime">The time the file was last written to.</param>
194+
/// <param name="changeTime">The time the file was last changed.</param>
195+
/// <param name="symlinkTargetOrNull">Specifies the symlink target path if the file is a symlink.</param>
196+
/// <returns>
197+
/// <para>
198+
/// <c>true</c> if the entry was successfully added to the enumeration buffer, <c>false</c> otherwise.
199+
/// </para>
200+
/// </returns>
201+
/// <exception cref="System::ArgumentException">
202+
/// <paramref name="fileName"/> is null or empty.
203+
/// </exception>
204+
virtual bool Add(
205+
System::String^ fileName,
206+
long long fileSize,
207+
bool isDirectory,
208+
System::IO::FileAttributes fileAttributes,
209+
System::DateTime creationTime,
210+
System::DateTime lastAccessTime,
211+
System::DateTime lastWriteTime,
212+
System::DateTime changeTime,
213+
System::String^ symlinkTargetOrNull) sealed
214+
{
215+
// This API is supported in Windows 10 version 2004 and above.
216+
if ((symlinkTargetOrNull != nullptr) &&
217+
(m_apiHelper->SupportedApi < ApiLevel::v2004))
218+
{
219+
throw gcnew NotImplementedException("PrjFillDirEntryBuffer2 is not supported in this version of Windows.");
220+
}
221+
222+
ValidateFileName(fileName);
223+
224+
pin_ptr<const WCHAR> pFileName = PtrToStringChars(fileName);
225+
PRJ_FILE_BASIC_INFO basicInfo = BuildFileBasicInfo(fileSize,
226+
isDirectory,
227+
fileAttributes,
228+
creationTime,
229+
lastAccessTime,
230+
lastWriteTime,
231+
changeTime);
232+
233+
PRJ_EXTENDED_INFO extendedInfo = {};
234+
if (symlinkTargetOrNull != nullptr)
235+
{
236+
extendedInfo.InfoType = PRJ_EXT_INFO_TYPE_SYMLINK;
237+
pin_ptr<const WCHAR> targetPath = PtrToStringChars(symlinkTargetOrNull);
238+
extendedInfo.Symlink.TargetName = targetPath;
239+
}
240+
241+
HRESULT hr;
242+
hr = m_apiHelper->_PrjFillDirEntryBuffer2(m_dirEntryBufferHandle,
243+
pFileName,
244+
&basicInfo,
245+
(symlinkTargetOrNull != nullptr) ? &extendedInfo : nullptr);
246+
247+
if FAILED(hr)
248+
{
249+
return false;
250+
}
251+
252+
return true;
253+
}
254+
255+
private:
256+
257+
PRJ_DIR_ENTRY_BUFFER_HANDLE m_dirEntryBufferHandle;
258+
ApiHelper^ m_apiHelper;
259+
260+
void ValidateFileName(System::String^ fileName)
142261
{
143262
if (System::String::IsNullOrEmpty(fileName))
144263
{
145264
throw gcnew System::ArgumentException(System::String::Format(System::Globalization::CultureInfo::InvariantCulture,
146-
"fileName cannot be empty."));
265+
"fileName cannot be empty."));
147266
}
267+
}
148268

149-
pin_ptr<const WCHAR> pFileName = PtrToStringChars(fileName);
269+
PRJ_FILE_BASIC_INFO BuildFileBasicInfo(long long fileSize,
270+
bool isDirectory,
271+
System::IO::FileAttributes fileAttributes,
272+
System::DateTime creationTime,
273+
System::DateTime lastAccessTime,
274+
System::DateTime lastWriteTime,
275+
System::DateTime changeTime)
276+
{
150277
PRJ_FILE_BASIC_INFO basicInfo = { 0 };
151278

152279
if (creationTime != System::DateTime::MinValue)
@@ -173,20 +300,7 @@ public ref class DirectoryEnumerationResults : public IDirectoryEnumerationResul
173300
basicInfo.IsDirectory = isDirectory;
174301
basicInfo.FileSize = fileSize;
175302

176-
auto hr = ::PrjFillDirEntryBuffer(pFileName,
177-
&basicInfo,
178-
m_dirEntryBufferHandle);
179-
180-
if FAILED(hr)
181-
{
182-
return false;
183-
}
184-
185-
return true;
303+
return basicInfo;
186304
}
187-
188-
private:
189-
190-
PRJ_DIR_ENTRY_BUFFER_HANDLE m_dirEntryBufferHandle;
191305
};
192306
}}} // namespace Microsoft.Windows.ProjFS

ProjectedFSLib.Managed.API/IDirectoryEnumerationResults.h

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,62 @@ public interface class IDirectoryEnumerationResults
107107
System::DateTime lastWriteTime,
108108
System::DateTime changeTime
109109
);
110+
111+
/// <summary>
112+
/// When overridden in a derived class, adds one entry to a directory enumeration result.
113+
/// </summary>
114+
/// <remarks>
115+
/// <para>
116+
/// In its implementation of a <c>GetDirectoryEnumerationCallback</c> delegate the provider
117+
/// calls this method for each matching file or directory in the enumeration.
118+
/// </para>
119+
/// <para>
120+
/// If this method returns <c>false</c>, the provider returns <c>HResult.Ok</c> and waits for
121+
/// the next <c>GetDirectoryEnumerationCallback</c>. Then it resumes filling the enumeration with
122+
/// the entry it was trying to add when it got <c>false</c>.
123+
/// </para>
124+
/// <para>
125+
/// If the function returns <c>false</c> for the first file or directory in the enumeration, the
126+
/// provider returns <c>HResult.InsufficientBuffer</c> from the <c>GetDirectoryEnumerationCallback</c>
127+
/// method.
128+
/// </para>
129+
/// <para>
130+
/// IMPORTANT: File and directory names passed to this method must be in the sort
131+
/// specified by <c>PrjFileNameCompare</c>
132+
/// (see https://docs.microsoft.com/en-us/windows/win32/api/projectedfslib/nf-projectedfslib-prjfilenamecompare ),
133+
/// or else names can be duplicated or missing from the enumeration results presented to the
134+
/// process enumerating the filesystem.
135+
/// </para>
136+
/// <para>
137+
/// This overload is incompatible with .NET Framework clients.
138+
/// See https://github.com/microsoft/ProjFS-Managed-API/issues/81 for details.
139+
/// </para>
140+
/// </remarks>
141+
/// <param name="fileName">The name of the file or directory.</param>
142+
/// <param name="fileSize">The size of the file.</param>
143+
/// <param name="isDirectory"><c>true</c> if this item is a directory, <c>false</c> if it is a file.</param>
144+
/// <param name="fileAttributes">The file attributes.</param>
145+
/// <param name="creationTime">Specifies the time that the file was created.</param>
146+
/// <param name="lastAccessTime">Specifies the time that the file was last accessed.</param>
147+
/// <param name="lastWriteTime">Specifies the time that the file was last written to.</param>
148+
/// <param name="changeTime">Specifies the last time the file was changed.</param>
149+
/// <param name="symlinkTargetOrNull">Specifies the symlink target path if the file is a symlink.</param>
150+
/// <returns>
151+
/// <para>
152+
/// <c>true</c> if the entry was successfully added to the enumeration buffer, <c>false</c> otherwise.
153+
/// </para>
154+
/// </returns>
155+
bool Add(
156+
System::String ^ fileName,
157+
long long fileSize,
158+
bool isDirectory,
159+
System::IO::FileAttributes fileAttributes,
160+
System::DateTime creationTime,
161+
System::DateTime lastAccessTime,
162+
System::DateTime lastWriteTime,
163+
System::DateTime changeTime,
164+
System::String ^ symlinkTargetOrNull
165+
);
110166
};
111167

112168
}}} // namespace Microsoft.Windows.ProjFS

0 commit comments

Comments
 (0)