Skip to content

Commit

Permalink
Merge pull request #73 from yerudako/user/yerudako/AddPrjInfo2Wrapper
Browse files Browse the repository at this point in the history
Enable symlinks in ProjFS-Managed
  • Loading branch information
cgallred authored Dec 6, 2021
2 parents b6fbd6b + 12a50c8 commit 5d047db
Show file tree
Hide file tree
Showing 16 changed files with 1,184 additions and 83 deletions.
32 changes: 28 additions & 4 deletions ProjectedFSLib.Managed.API/ApiHelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ using namespace System::IO;
using namespace Microsoft::Windows::ProjFS;

ApiHelper::ApiHelper() :
useRS5Api(false)
supportedApi(ApiLevel::v1803)
{
auto projFsLib = ::LoadLibraryW(L"ProjectedFSLib.dll");
if (!projFsLib)
Expand All @@ -22,6 +22,7 @@ ApiHelper::ApiHelper() :
if (::GetProcAddress(projFsLib, "PrjStartVirtualizing") != nullptr)
{
// We have the API introduced in Windows 10 version 1809.
this->supportedApi = ApiLevel::v1809;

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

this->_PrjMarkDirectoryAsPlaceholder = reinterpret_cast<t_PrjMarkDirectoryAsPlaceholder>(::GetProcAddress(projFsLib,
"PrjMarkDirectoryAsPlaceholder"));
if (::GetProcAddress(projFsLib, "PrjWritePlaceholderInfo2") != nullptr)
{
// We have the API introduced in Windows 10 version 2004.
this->supportedApi = ApiLevel::v2004;

this->_PrjWritePlaceholderInfo2 = reinterpret_cast<t_PrjWritePlaceholderInfo2>(::GetProcAddress(projFsLib,
"PrjWritePlaceholderInfo2"));

this->_PrjFillDirEntryBuffer2 = reinterpret_cast<t_PrjFillDirEntryBuffer2>(::GetProcAddress(projFsLib, "PrjFillDirEntryBuffer2"));
}

::FreeLibrary(projFsLib);

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

this->useRS5Api = true;
if (this->supportedApi >= ApiLevel::v2004)
{
if (!this->_PrjWritePlaceholderInfo2 ||
!this->_PrjFillDirEntryBuffer2)
{
throw gcnew EntryPointNotFoundException(String::Format(CultureInfo::InvariantCulture,
"Could not get a required entry point."));
}
}
}
else if (::GetProcAddress(projFsLib, "PrjStartVirtualizationInstance") == nullptr)
{
Expand Down Expand Up @@ -127,7 +146,12 @@ ApiHelper::ApiHelper() :
}
}

bool ApiHelper::UseRS5Api::get(void)
bool ApiHelper::UseBetaApi::get(void)
{
return (this->supportedApi == ApiLevel::v1803);
}

ApiLevel ApiHelper::SupportedApi::get(void)
{
return this->useRS5Api;
return this->supportedApi;
}
48 changes: 45 additions & 3 deletions ProjectedFSLib.Managed.API/ApiHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@
namespace Microsoft {
namespace Windows {
namespace ProjFS {
/// <summary>
/// Defines values describing the APIs available from this wrapper.
/// </summary>
enum class ApiLevel : short
{
/// <summary>Using Windows 10 version 1803 beta API.</summary>
v1803 = 1803,

/// <summary>Using Windows 10 version 1809 API.</summary>
v1809 = 1809,

/// <summary>Adding APIs introduced in Windows 10 version 2004.</summary>
v2004 = 2004,
};
/// <summary>Helper class for using the correct native APIs in the managed layer.</summary>
/// <remarks>
/// <para>
Expand All @@ -32,13 +46,37 @@ ref class ApiHelper {
internal:
ApiHelper();

property bool UseRS5Api
property bool UseBetaApi
{
bool get(void);
};
}

property ApiLevel SupportedApi
{
ApiLevel get(void);
}

private:

#pragma region Signatures for Windows 10 version 2004 APIs

typedef HRESULT(__stdcall* t_PrjWritePlaceholderInfo2)(
_In_ PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT namespaceVirtualizationContext,
_In_ PCWSTR destinationFileName,
_In_reads_bytes_(placeholderInfoSize) const PRJ_PLACEHOLDER_INFO* placeholderInfo,
_In_ UINT32 placeholderInfoSize,
_In_opt_ const PRJ_EXTENDED_INFO* ExtendedInfo
);

typedef HRESULT(__stdcall* t_PrjFillDirEntryBuffer2) (
_In_ PRJ_DIR_ENTRY_BUFFER_HANDLE dirEntryBufferHandle,
_In_ PCWSTR fileName,
_In_opt_ PRJ_FILE_BASIC_INFO* fileBasicInfo,
_In_opt_ PRJ_EXTENDED_INFO* extendedInfo
);

#pragma endregion

#pragma region Signatures for Windows 10 version 1809 APIs

typedef HRESULT (__stdcall* t_PrjStartVirtualizing)(
Expand Down Expand Up @@ -169,10 +207,14 @@ ref class ApiHelper {

#pragma endregion

bool useRS5Api;
ApiLevel supportedApi{ ApiLevel::v1803 };

internal:

// 2004 API
t_PrjWritePlaceholderInfo2 _PrjWritePlaceholderInfo2 = nullptr;
t_PrjFillDirEntryBuffer2 _PrjFillDirEntryBuffer2 = nullptr;

// 1809 API
t_PrjStartVirtualizing _PrjStartVirtualizing = nullptr;
t_PrjStopVirtualizing _PrjStopVirtualizing = nullptr;
Expand Down
154 changes: 134 additions & 20 deletions ProjectedFSLib.Managed.API/DirectoryEnumerationResults.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
#pragma once

#include "IDirectoryEnumerationResults.h"
#include "ApiHelper.h"

using namespace System;
using namespace System::Globalization;
using namespace System::IO;

namespace Microsoft {
namespace Windows {
Expand All @@ -18,10 +23,9 @@ namespace ProjFS {
public ref class DirectoryEnumerationResults : public IDirectoryEnumerationResults {
internal:

DirectoryEnumerationResults(PRJ_DIR_ENTRY_BUFFER_HANDLE bufferHandle)
{
m_dirEntryBufferHandle = bufferHandle;
}
DirectoryEnumerationResults(PRJ_DIR_ENTRY_BUFFER_HANDLE bufferHandle, ApiHelper^ apiHelper) :
m_dirEntryBufferHandle(bufferHandle), m_apiHelper(apiHelper)
{ }

// Provides access to the native handle to the directory entry buffer.
// Used internally by VirtualizationInstance::CompleteCommand(int, IDirectoryEnumerationResults^).
Expand Down Expand Up @@ -139,14 +143,137 @@ public ref class DirectoryEnumerationResults : public IDirectoryEnumerationResul
System::DateTime lastAccessTime,
System::DateTime lastWriteTime,
System::DateTime changeTime) sealed
{
ValidateFileName(fileName);

pin_ptr<const WCHAR> pFileName = PtrToStringChars(fileName);
PRJ_FILE_BASIC_INFO basicInfo = BuildFileBasicInfo(fileSize,
isDirectory,
fileAttributes,
creationTime,
lastAccessTime,
lastWriteTime,
changeTime);

auto hr = ::PrjFillDirEntryBuffer(pFileName,
&basicInfo,
m_dirEntryBufferHandle);

if FAILED(hr)
{
return false;
}

return true;
}

/// <summary>Adds one entry to a directory enumeration result.</summary>
/// <remarks>
/// <para>
/// In its implementation of a <c>GetDirectoryEnumerationCallback</c> delegate the provider
/// calls this method for each matching file or directory in the enumeration.
/// </para>
/// <para>
/// If this method returns <c>false</c>, the provider returns <see cref="HResult::Ok"/> and waits for
/// the next <c>GetDirectoryEnumerationCallback</c>. Then it resumes filling the enumeration with
/// the entry it was trying to add when it got <c>false</c>.
/// </para>
/// <para>
/// If the method returns <c>false</c> for the first file or directory in the enumeration, the
/// provider returns <see cref="HResult::InsufficientBuffer"/> from the <c>GetDirectoryEnumerationCallback</c>
/// method.
/// </para>
/// </remarks>
/// <param name="fileName">The name of the file or directory.</param>
/// <param name="fileSize">The size of the file.</param>
/// <param name="isDirectory"><c>true</c> if this item is a directory, <c>false</c> if it is a file.</param>
/// <param name="fileAttributes">The file attributes.</param>
/// <param name="creationTime">The time the file was created.</param>
/// <param name="lastAccessTime">The time the file was last accessed.</param>
/// <param name="lastWriteTime">The time the file was last written to.</param>
/// <param name="changeTime">The time the file was last changed.</param>
/// <param name="symlinkTargetOrNull">Specifies the symlink target path if the file is a symlink.</param>
/// <returns>
/// <para>
/// <c>true</c> if the entry was successfully added to the enumeration buffer, <c>false</c> otherwise.
/// </para>
/// </returns>
/// <exception cref="System::ArgumentException">
/// <paramref name="fileName"/> is null or empty.
/// </exception>
virtual bool Add(
System::String^ fileName,
long long fileSize,
bool isDirectory,
System::IO::FileAttributes fileAttributes,
System::DateTime creationTime,
System::DateTime lastAccessTime,
System::DateTime lastWriteTime,
System::DateTime changeTime,
System::String^ symlinkTargetOrNull) sealed
{
// This API is supported in Windows 10 version 2004 and above.
if ((symlinkTargetOrNull != nullptr) &&
(m_apiHelper->SupportedApi < ApiLevel::v2004))
{
throw gcnew NotImplementedException("PrjFillDirEntryBuffer2 is not supported in this version of Windows.");
}

ValidateFileName(fileName);

pin_ptr<const WCHAR> pFileName = PtrToStringChars(fileName);
PRJ_FILE_BASIC_INFO basicInfo = BuildFileBasicInfo(fileSize,
isDirectory,
fileAttributes,
creationTime,
lastAccessTime,
lastWriteTime,
changeTime);

PRJ_EXTENDED_INFO extendedInfo = {};
if (symlinkTargetOrNull != nullptr)
{
extendedInfo.InfoType = PRJ_EXT_INFO_TYPE_SYMLINK;
pin_ptr<const WCHAR> targetPath = PtrToStringChars(symlinkTargetOrNull);
extendedInfo.Symlink.TargetName = targetPath;
}

HRESULT hr;
hr = m_apiHelper->_PrjFillDirEntryBuffer2(m_dirEntryBufferHandle,
pFileName,
&basicInfo,
(symlinkTargetOrNull != nullptr) ? &extendedInfo : nullptr);

if FAILED(hr)
{
return false;
}

return true;
}

private:

PRJ_DIR_ENTRY_BUFFER_HANDLE m_dirEntryBufferHandle;
ApiHelper^ m_apiHelper;

void ValidateFileName(System::String^ fileName)
{
if (System::String::IsNullOrEmpty(fileName))
{
throw gcnew System::ArgumentException(System::String::Format(System::Globalization::CultureInfo::InvariantCulture,
"fileName cannot be empty."));
"fileName cannot be empty."));
}
}

pin_ptr<const WCHAR> pFileName = PtrToStringChars(fileName);
PRJ_FILE_BASIC_INFO BuildFileBasicInfo(long long fileSize,
bool isDirectory,
System::IO::FileAttributes fileAttributes,
System::DateTime creationTime,
System::DateTime lastAccessTime,
System::DateTime lastWriteTime,
System::DateTime changeTime)
{
PRJ_FILE_BASIC_INFO basicInfo = { 0 };

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

auto hr = ::PrjFillDirEntryBuffer(pFileName,
&basicInfo,
m_dirEntryBufferHandle);

if FAILED(hr)
{
return false;
}

return true;
return basicInfo;
}

private:

PRJ_DIR_ENTRY_BUFFER_HANDLE m_dirEntryBufferHandle;
};
}}} // namespace Microsoft.Windows.ProjFS
56 changes: 56 additions & 0 deletions ProjectedFSLib.Managed.API/IDirectoryEnumerationResults.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,62 @@ public interface class IDirectoryEnumerationResults
System::DateTime lastWriteTime,
System::DateTime changeTime
);

/// <summary>
/// When overridden in a derived class, adds one entry to a directory enumeration result.
/// </summary>
/// <remarks>
/// <para>
/// In its implementation of a <c>GetDirectoryEnumerationCallback</c> delegate the provider
/// calls this method for each matching file or directory in the enumeration.
/// </para>
/// <para>
/// If this method returns <c>false</c>, the provider returns <c>HResult.Ok</c> and waits for
/// the next <c>GetDirectoryEnumerationCallback</c>. Then it resumes filling the enumeration with
/// the entry it was trying to add when it got <c>false</c>.
/// </para>
/// <para>
/// If the function returns <c>false</c> for the first file or directory in the enumeration, the
/// provider returns <c>HResult.InsufficientBuffer</c> from the <c>GetDirectoryEnumerationCallback</c>
/// method.
/// </para>
/// <para>
/// IMPORTANT: File and directory names passed to this method must be in the sort
/// specified by <c>PrjFileNameCompare</c>
/// (see https://docs.microsoft.com/en-us/windows/win32/api/projectedfslib/nf-projectedfslib-prjfilenamecompare ),
/// or else names can be duplicated or missing from the enumeration results presented to the
/// process enumerating the filesystem.
/// </para>
/// <para>
/// This overload is incompatible with .NET Framework clients.
/// See https://github.com/microsoft/ProjFS-Managed-API/issues/81 for details.
/// </para>
/// </remarks>
/// <param name="fileName">The name of the file or directory.</param>
/// <param name="fileSize">The size of the file.</param>
/// <param name="isDirectory"><c>true</c> if this item is a directory, <c>false</c> if it is a file.</param>
/// <param name="fileAttributes">The file attributes.</param>
/// <param name="creationTime">Specifies the time that the file was created.</param>
/// <param name="lastAccessTime">Specifies the time that the file was last accessed.</param>
/// <param name="lastWriteTime">Specifies the time that the file was last written to.</param>
/// <param name="changeTime">Specifies the last time the file was changed.</param>
/// <param name="symlinkTargetOrNull">Specifies the symlink target path if the file is a symlink.</param>
/// <returns>
/// <para>
/// <c>true</c> if the entry was successfully added to the enumeration buffer, <c>false</c> otherwise.
/// </para>
/// </returns>
bool Add(
System::String ^ fileName,
long long fileSize,
bool isDirectory,
System::IO::FileAttributes fileAttributes,
System::DateTime creationTime,
System::DateTime lastAccessTime,
System::DateTime lastWriteTime,
System::DateTime changeTime,
System::String ^ symlinkTargetOrNull
);
};

}}} // namespace Microsoft.Windows.ProjFS
Loading

0 comments on commit 5d047db

Please sign in to comment.