From 85beb9aae2c91d30a533b775cf9700f9656f09bc Mon Sep 17 00:00:00 2001 From: Jess Kube <44536565+jessk-msft@users.noreply.github.com> Date: Sun, 9 Jul 2023 17:17:50 -0700 Subject: [PATCH] 1.26.0.0 release (#25) * official driver altitude * version 1.26.0.0 * initial login prompts for sso * Update p4vfs-verify.yml * Update p4vfs-verify.yml * Update p4vfs-verify.yml * Update p4vfs-verify.yml * Update p4vfs-verify.yml * Update p4vfs-verify.yml * Update p4vfs-verify.yml * Update p4vfs-verify.yml * Update p4vfs-verify.yml * Update p4vfs-verify.yml * Update p4vfs-verify.yml * Update p4vfs-verify.yml * Update p4vfs-verify.yml * Update p4vfs-verify.yml * reverting configuration actions * login unit test fixes * fixing unit tests and removing unnecessary impersonation * DepotClient HandleUrl for HAS auth * service environment launch fix * build *Dev configurations in dev/* branches * Update p4vfs-verify.yml * configuration * Update p4vfs-verify.yml * Update p4vfs-verify.yml * Update p4vfs-verify.yml * Update p4vfs-verify.yml * reverting p4vfs-verify.yml to main * Update p4vfs-verify.yml --- .github/workflows/p4vfs-verify.yml | 2 +- external/P4VFS/P4VFS.Module.cs | 2 +- source/P4VFS.Console/P4VFS.Notes.txt | 9 + source/P4VFS.Console/Source/Program.cs | 2 +- source/P4VFS.Core/Include/DepotClient.h | 14 +- source/P4VFS.Core/Include/DepotClientCache.h | 2 +- source/P4VFS.Core/Include/DepotCommand.h | 5 +- source/P4VFS.Core/Include/SettingManager.h | 2 +- source/P4VFS.Core/Source/DepotClient.cpp | 202 +++++++++++++----- source/P4VFS.Core/Source/DepotClientCache.cpp | 9 +- source/P4VFS.Core/Source/DepotOperations.cpp | 8 +- source/P4VFS.Core/Source/FileCore.cpp | 15 +- .../P4VFS.CoreInterop/Include/CoreMarshal.h | 5 + .../Include/DepotClientInterop.h | 4 +- .../Source/DepotClientInterop.cpp | 30 ++- source/P4VFS.Driver/Include/DriverVersion.h | 4 +- source/P4VFS.Driver/p4vfsflt.inf | 2 +- source/P4VFS.Service/Source/ServiceHost.cpp | 3 +- source/P4VFS.UnitTest/Source/UnitTestBase.cs | 18 ++ .../P4VFS.UnitTest/Source/UnitTestCommon.cs | 76 ++++--- .../P4VFS.UnitTest/Source/UnitTestRemote.cs | 4 +- .../P4VFS.UnitTest/Source/UnitTestServer.cs | 9 +- 22 files changed, 304 insertions(+), 123 deletions(-) diff --git a/.github/workflows/p4vfs-verify.yml b/.github/workflows/p4vfs-verify.yml index 2d77f41..bd1593a 100644 --- a/.github/workflows/p4vfs-verify.yml +++ b/.github/workflows/p4vfs-verify.yml @@ -29,7 +29,7 @@ jobs: strategy: matrix: os: [windows-latest] - configuration: [Debug, Release] + configuration: [DebugDev, ReleaseDev] steps: - name: Set image info in env diff --git a/external/P4VFS/P4VFS.Module.cs b/external/P4VFS/P4VFS.Module.cs index 001ab2a..729e318 100644 --- a/external/P4VFS/P4VFS.Module.cs +++ b/external/P4VFS/P4VFS.Module.cs @@ -11,7 +11,7 @@ namespace Microsoft.P4VFS.External { public class P4vfsModule : Module { - private const string P4VFS_SIGNED_VERSION = "1.25.0.0"; + private const string P4VFS_SIGNED_VERSION = "1.26.0.0"; private const string P4VFS_SIGNED_ARTIFACTS_URL = "https://github.com/microsoft/p4vfs/releases/download"; public override string Name diff --git a/source/P4VFS.Console/P4VFS.Notes.txt b/source/P4VFS.Console/P4VFS.Notes.txt index 1a9f12b..928f9c9 100644 --- a/source/P4VFS.Console/P4VFS.Notes.txt +++ b/source/P4VFS.Console/P4VFS.Notes.txt @@ -1,5 +1,14 @@ Microsoft P4VFS Release Notes +Version [1.26.0.0] +* Driver using officially allocated FSFilter HSM altitude of 189700 +* Refactor of perforce client login operations to support common forms of password and + SSO login. This includes password auth, classic P4LOGINSSO client script auth, and server + auth extensions such as Helix Authentication Service. +* Fixing service process launch as impersonated user to include the user's profile environment. + This allows the service's interactive password prompt, or browser authentication, to execute + with expected user environment variables. + Version [1.25.1.0] * Addition of new "hydrate" command as synonym for "resident -x". Moved the implementation into C++ and now using a single fstat command instead of a files & where command. diff --git a/source/P4VFS.Console/Source/Program.cs b/source/P4VFS.Console/Source/Program.cs index b7e2a45..8607405 100644 --- a/source/P4VFS.Console/Source/Program.cs +++ b/source/P4VFS.Console/Source/Program.cs @@ -1016,7 +1016,7 @@ private static bool CommandLogin(string[] args) using (DepotClient depotClient = new DepotClient()) { depotClient.Unattended = true; - return depotClient.Connect(_P4Port, _P4Client, _P4User, _P4Directory, _P4Passwd, _P4Host) && depotClient.Login(_P4Passwd); + return depotClient.Connect(_P4Port, _P4Client, _P4User, _P4Directory, _P4Passwd, _P4Host) && depotClient.Login((_) => _P4Passwd); } }; diff --git a/source/P4VFS.Core/Include/DepotClient.h b/source/P4VFS.Core/Include/DepotClient.h index a8d7e12..7022aff 100644 --- a/source/P4VFS.Core/Include/DepotClient.h +++ b/source/P4VFS.Core/Include/DepotClient.h @@ -16,8 +16,7 @@ namespace P4 { typedef std::shared_ptr DepotClient; typedef std::function FDepotClientLogCallback; - typedef FDepotClientLogCallback FOnErrorCallback; - typedef FDepotClientLogCallback FOnMessageCallback; + typedef std::shared_ptr DepotClientLogCallback; struct FDepotClient { @@ -34,7 +33,7 @@ namespace P4 { P4VFS_CORE_API bool IsConnectedClient(); P4VFS_CORE_API bool IsLoginRequired(); - P4VFS_CORE_API bool Login(const DepotString& passwd); + P4VFS_CORE_API bool Login(DepotClientPromptCallback prompt); P4VFS_CORE_API bool Login(); P4VFS_CORE_API DepotResult Trust(); @@ -61,10 +60,10 @@ namespace P4 { template Result Run(const DepotString& name, const DepotStringArray& args = DepotStringArray()); - virtual bool OnRequestPassword(DepotString& passwd); virtual void OnErrorPause(const char* message); virtual void OnErrorCallback(LogChannel::Enum channel, const char* severity, const char* text); virtual void OnMessageCallback(LogChannel::Enum channel, const char* severity, const char* text); + virtual DepotString OnPromptCallback(const DepotCommand* command, const char* message); P4VFS_CORE_API LogDevice* Log(); P4VFS_CORE_API void Log(LogChannel::Enum channel, const DepotString& text); @@ -93,15 +92,14 @@ namespace P4 { P4VFS_CORE_API DepotString GetEnvImpersonated(const char* name) const; P4VFS_CORE_API WString GetEnvImpersonatedW(const char* name) const; - P4VFS_CORE_API void SetErrorCallback(FOnErrorCallback* callback); - P4VFS_CORE_API void SetMessageCallback(FOnMessageCallback* callback); + P4VFS_CORE_API void SetErrorCallback(DepotClientLogCallback callback); + P4VFS_CORE_API void SetMessageCallback(DepotClientLogCallback callback); protected: bool LoginUsingConfig(); - bool LoginUsingSSO(); bool LoginUsingClientOwner(); - bool LoginUsingImpersonation(); bool LoginUsingInteractiveSession(); + bool RequestInteractivePassword(DepotString& passwd); private: struct Api; diff --git a/source/P4VFS.Core/Include/DepotClientCache.h b/source/P4VFS.Core/Include/DepotClientCache.h index 802a2f9..7660a77 100644 --- a/source/P4VFS.Core/Include/DepotClientCache.h +++ b/source/P4VFS.Core/Include/DepotClientCache.h @@ -20,6 +20,7 @@ namespace P4 { void GarbageCollect(int64_t timeoutSeconds); size_t GetFreeCount() const; + static int64_t GetIdleTimeoutSeconds(); private: static DepotString CreateKey(const DepotConfig& config); @@ -27,7 +28,6 @@ namespace P4 { private: typedef UnorderedMultiMap FreeMapType; - const int64_t m_KeepAliveSeconds; CriticalSection m_FreeMapLock; FreeMapType* m_FreeMap; }; diff --git a/source/P4VFS.Core/Include/DepotCommand.h b/source/P4VFS.Core/Include/DepotCommand.h index 028084f..5c33b44 100644 --- a/source/P4VFS.Core/Include/DepotCommand.h +++ b/source/P4VFS.Core/Include/DepotCommand.h @@ -8,6 +8,9 @@ namespace Microsoft { namespace P4VFS { namespace P4 { + typedef std::function FDepotClientPromptCallback; + typedef std::shared_ptr DepotClientPromptCallback; + struct DepotCommand { struct Flags { enum Enum { @@ -28,8 +31,8 @@ namespace P4 { DepotString m_Name; DepotStringArray m_Args; DepotString m_Input; - DepotString m_Prompt; Flags::Enum m_Flags; + DepotClientPromptCallback m_Prompt; }; struct IDepotClientCommand diff --git a/source/P4VFS.Core/Include/SettingManager.h b/source/P4VFS.Core/Include/SettingManager.h index 5a25548..9982c15 100644 --- a/source/P4VFS.Core/Include/SettingManager.h +++ b/source/P4VFS.Core/Include/SettingManager.h @@ -30,7 +30,7 @@ namespace FileCore { _N( int32_t, CreateFileRetryWaitMs, 250 ) \ _N( int32_t, PoolDefaultNumberOfThreads, 8 ) \ _N( int32_t, GarbageCollectPeriodMs, 5*60*1000 ) \ - _N( int32_t, DepotClientCacheIdleTimeoutMs, 10*60*1000 ) \ + _N( int32_t, DepotClientCacheIdleTimeoutMs, 5*60*1000 ) \ class SettingManager; diff --git a/source/P4VFS.Core/Source/DepotClient.cpp b/source/P4VFS.Core/Source/DepotClient.cpp index 8a1f912..29160b4 100644 --- a/source/P4VFS.Core/Source/DepotClient.cpp +++ b/source/P4VFS.Core/Source/DepotClient.cpp @@ -275,19 +275,24 @@ class DepotClientCommand : public ClientUser, IDepotClientCommand m_Result->OnStreamStat(this, TagList().back()); } - virtual void Prompt(const StrPtr& msg, StrBuf& rsp, int noEcho, Error* e) override + virtual void Prompt(const StrPtr& msg, StrBuf& rsp, int noEcho, Error* e) { - rsp = m_Command->m_Prompt.c_str(); + rsp = m_Client->OnPromptCallback(m_Command, msg.Text()).c_str(); } - virtual void Prompt(const StrPtr& msg, StrBuf& rsp, int noEcho, int noOutput, Error* e) override + virtual void ErrorPause(char* errBuf, Error* e) override { - rsp = m_Command->m_Prompt.c_str(); + m_Client->OnErrorPause(errBuf); } - virtual void ErrorPause(char* errBuf, Error* e) override + virtual void HandleUrl(const StrPtr* url) override { - m_Client->OnErrorPause(errBuf); + if (url != nullptr && FileCore::StringInfo::IsNullOrEmpty(url->Text()) == false) + { + UserContext* context = m_Client->GetUserContext(); + DepotString cmd = StringInfo::Format("cmd.exe /c start %s", url->Text()); + FileOperations::CreateProcessImpersonated(CSTR_ATOW(cmd), nullptr, FALSE, nullptr, context); + } } virtual void Diff(FileSys* f1, FileSys* f2, FileSys* fout, int doPage, char* diffFlags, Error* e) override @@ -382,8 +387,8 @@ struct FDepotClient::Api DepotTimer m_AccessTime; FileContext* m_FileContext; Flags::Enum m_Flags; - FOnErrorCallback* m_OnErrorCallback; - FOnMessageCallback* m_OnMessageCallback; + DepotClientLogCallback m_OnErrorCallback; + DepotClientLogCallback m_OnMessageCallback; }; FDepotClient::FDepotClient(FileContext* context) @@ -391,8 +396,8 @@ FDepotClient::FDepotClient(FileContext* context) m_P4 = new FDepotClient::Api(); m_P4->m_FileContext = context; m_P4->m_Flags = Flags::None; - m_P4->m_OnErrorCallback = nullptr; - m_P4->m_OnMessageCallback = nullptr; + m_P4->m_OnErrorCallback.reset(); + m_P4->m_OnMessageCallback.reset(); } FDepotClient::~FDepotClient() @@ -451,7 +456,9 @@ bool FDepotClient::Connect(const DepotConfig& config) if (HasFlag(Flags::DisableLogin) == false && IsLoginRequired()) { if (Login() == false) + { return false; + } } if (m_P4->m_Connection.get() == nullptr) @@ -504,7 +511,9 @@ bool FDepotClient::IsConnectedClient() bool FDepotClient::IsLoginRequired() { if (!IsConnected()) + { return false; + } m_P4->m_Connection = Client(); if (m_P4->m_Connection->HasErrorRegex("use the 'p4 trust' command")) @@ -515,19 +524,22 @@ bool FDepotClient::IsLoginRequired() m_P4->m_Config.Apply(m_P4->m_Connection->Config()); if (m_P4->m_Connection->Access().empty() == false) + { return false; + } if (Run("login", DepotStringArray{"-s"})->HasError() == false) + { return false; - + } return true; } -bool FDepotClient::Login(const DepotString& passwd) +bool FDepotClient::Login(DepotClientPromptCallback prompt) { DepotCommand cmd; cmd.m_Name = "login"; - cmd.m_Prompt = passwd; + cmd.m_Prompt = prompt; return Run(cmd)->HasError() == false; } @@ -575,7 +587,9 @@ DepotString FDepotClient::GetTicketsFilePath() const if (envTicketsPath.empty() == false) { if (FileInfo::CreateWritableFile(envTicketsPath.c_str())) + { return StringInfo::ToAnsi(FileInfo::FullPath(envTicketsPath.c_str())); + } } const WString profileFolder = GetEnvImpersonatedW(DepotConstants::USERPROFILE); @@ -583,7 +597,9 @@ DepotString FDepotClient::GetTicketsFilePath() const { WString profileTicketsPath = FileInfo::FullPath(StringInfo::Format(L"%s\\p4tickets.txt", profileFolder.c_str()).c_str()); if (FileInfo::CreateWritableFile(profileTicketsPath.c_str())) + { return StringInfo::ToAnsi(profileTicketsPath); + } } wchar_t userName[512] = {0}; @@ -592,7 +608,9 @@ DepotString FDepotClient::GetTicketsFilePath() const { WString userTicketsPath = StringInfo::Format(L"C:\\Users\\%s\\p4tickets.txt", userName); if (FileInfo::CreateWritableFile(userTicketsPath.c_str())) + { return StringInfo::ToAnsi(userTicketsPath); + } } // Absolute last resort... this may be the service profile folder @@ -600,7 +618,9 @@ DepotString FDepotClient::GetTicketsFilePath() const if (ExpandEnvironmentStrings(L"%USERPROFILE%\\p4tickets.txt", currentProfileFolder, _countof(currentProfileFolder)) != 0) { if (FileInfo::CreateWritableFile(currentProfileFolder)) + { return StringInfo::ToAnsi(currentProfileFolder); + } } return DepotString(); } @@ -611,7 +631,9 @@ DepotString FDepotClient::GetTrustFilePath() const if (envTrustPath.empty() == false) { if (FileInfo::CreateWritableFile(envTrustPath.c_str())) + { return StringInfo::ToAnsi(FileInfo::FullPath(envTrustPath.c_str())); + } } const WString profileFolder = GetEnvImpersonatedW(DepotConstants::USERPROFILE); @@ -619,7 +641,9 @@ DepotString FDepotClient::GetTrustFilePath() const { WString profileTicketsPath = FileInfo::FullPath(StringInfo::Format(L"%s\\p4trust.txt", profileFolder.c_str()).c_str()); if (FileInfo::CreateWritableFile(profileTicketsPath.c_str())) + { return StringInfo::ToAnsi(profileTicketsPath); + } } wchar_t userName[512] = {0}; @@ -628,7 +652,9 @@ DepotString FDepotClient::GetTrustFilePath() const { WString userTicketsPath = StringInfo::Format(L"C:\\Users\\%s\\p4trust.txt", userName); if (FileInfo::CreateWritableFile(userTicketsPath.c_str())) + { return StringInfo::ToAnsi(userTicketsPath); + } } // Absolute last resort... this may be the service profile folder @@ -636,7 +662,9 @@ DepotString FDepotClient::GetTrustFilePath() const if (ExpandEnvironmentStrings(L"%USERPROFILE%\\p4trust.txt", currentProfileFolder, _countof(currentProfileFolder)) != 0) { if (FileInfo::CreateWritableFile(currentProfileFolder)) + { return StringInfo::ToAnsi(currentProfileFolder); + } } return DepotString(); } @@ -644,18 +672,24 @@ DepotString FDepotClient::GetTrustFilePath() const DepotString FDepotClient::GetClientOwnerUserName(const DepotString& clientName, const DepotString& portName) { if (StringInfo::IsNullOrEmpty(clientName.c_str())) + { return DepotString(); + } Array tickets; if (FileInfo::ReadFileLines(StringInfo::AtoW(GetTicketsFilePath()), tickets) == false) + { return DepotString(); + } std::set depotUsers; for (const DepotString& line : tickets) { std::match_results match; if (std::regex_search(line.c_str(), match, std::regex("=\\s*(.+?)\\s*:"))) + { depotUsers.insert(match[1]); + } } for (const DepotString& depotUser : depotUsers) @@ -669,10 +703,14 @@ DepotString FDepotClient::GetClientOwnerUserName(const DepotString& clientName, localConfig.m_Port = portName; localConfig.m_User = depotUser; if (localClient.Connect(localConfig) == false) + { continue; + } DepotString ownerName = localClient.Client()->Owner(); if (ownerName.empty() == false) + { return ownerName; + } } return DepotString(); } @@ -680,16 +718,22 @@ DepotString FDepotClient::GetClientOwnerUserName(const DepotString& clientName, DepotString FDepotClient::GetHostName() const { if (m_P4->m_Config.m_Host.empty() == false) + { return m_P4->m_Config.m_Host; + } DepotString envHostName = GetEnvImpersonated(DepotConstants::P4HOST); if (envHostName.empty() == false) + { return envHostName; + } char strHostName[1024] = {0}; DWORD dwHostNameLen = _countof(strHostName)-1; if (GetComputerNameA(strHostName, &dwHostNameLen)) + { return strHostName; + } return GetEnv(DepotConstants::COMPUTERNAME); } @@ -697,100 +741,135 @@ DepotString FDepotClient::GetHostName() const bool FDepotClient::Login() { if (LoginUsingConfig()) + { return true; - if (LoginUsingSSO()) - return true; + } if (LoginUsingClientOwner()) + { return true; - if (LoginUsingImpersonation()) - return true; + } if (LoginUsingInteractiveSession()) + { return true; + } return false; } bool FDepotClient::LoginUsingConfig() { if (m_P4->m_Config.m_Passwd.empty()) - return false; - if (Login(m_P4->m_Config.m_Passwd) == false) - return false; - if (IsLoginRequired()) - return false; - return true; -} + { + m_P4->m_Config.m_Passwd = GetEnvImpersonated(DepotConstants::P4PASSWD); + } -bool FDepotClient::LoginUsingSSO() -{ - const DepotString loginSSO = GetEnvImpersonated(DepotConstants::P4LOGINSSO); - if (loginSSO.empty()) - return false; - if (SetEnv(DepotConstants::P4LOGINSSO, loginSSO.c_str()) == false) - return false; - if (Login("") == false) + DepotClientPromptCallback prompt = std::make_shared([this](const DepotString& message) -> DepotString + { + if (StringInfo::Contains(message.c_str(), "password", StringInfo::SearchCase::Insensitive)) + { + return m_P4->m_Config.m_Passwd; + } + return DepotString(); + }); + + if (Login(prompt) == false) + { return false; + } + if (IsLoginRequired()) + { return false; + } return true; } bool FDepotClient::LoginUsingClientOwner() { if (IsConnected() == false) + { return false; + } + DepotString ownerUserName = GetClientOwnerUserName(m_P4->m_Config.m_Client, m_P4->m_Config.m_Port); if (ownerUserName.empty() || ownerUserName == m_P4->m_Config.m_User) + { return false; + } + m_P4->m_ClientApi->Final(m_P4->m_Error.get()); if (HasError()) + { return false; + } + m_P4->m_Config.m_User = ownerUserName; m_P4->m_Config.m_Passwd.clear(); m_P4->m_ClientApi->SetUser(m_P4->m_Config.m_User.c_str()); m_P4->m_ClientApi->SetPassword(m_P4->m_Config.m_Passwd.c_str()); m_P4->m_ClientApi->Init(m_P4->m_Error.get()); if (HasError()) + { return false; - if (IsLoginRequired()) - return false; - return true; -} + } -bool FDepotClient::LoginUsingImpersonation() -{ - if (HasFlag(Flags::Unimpersonated)) - return false; - const DepotString passwd = GetEnvImpersonated(DepotConstants::P4PASSWD); - if (passwd.empty()) - return false; - if (Login(passwd) == false) - return false; if (IsLoginRequired()) + { return false; + } return true; } bool FDepotClient::LoginUsingInteractiveSession() { if (HasFlag(Flags::Unattended)) + { return false; + } + if (FileCore::SettingManager::StaticInstance().Unattended.GetValue()) + { return false; - const UserContext* context = m_P4->m_FileContext ? m_P4->m_FileContext->m_UserContext : nullptr; + } + + const UserContext* context = GetUserContext(); if (FileOperations::IsSystemUserContext(context)) + { return false; + } + DepotResult ticket = Run("login", DepotStringArray{"-s"}); if (ticket->HasError() == false) + { return true; + } + if (ticket->HasErrorRegex("user .+ doesn't exist")) + { return false; - DepotString passwd; - if (OnRequestPassword(passwd) == false) - return false; - if (Login(passwd) == false) + } + + DepotClientPromptCallback prompt = std::make_shared([this](const DepotString& message) -> DepotString + { + if (StringInfo::Contains(message.c_str(), "password", StringInfo::SearchCase::Insensitive)) + { + DepotString passwd; + if (RequestInteractivePassword(passwd)) + { + return passwd; + } + } + return DepotString(); + }); + + if (Login(prompt) == false) + { return false; + } + if (IsLoginRequired()) + { return false; + } return true; } @@ -829,7 +908,7 @@ void FDepotClient::Run(const DepotCommand& cmd, FDepotResult& result) result.OnComplete(); } -bool FDepotClient::OnRequestPassword(DepotString& passwd) +bool FDepotClient::RequestInteractivePassword(DepotString& passwd) { WString output; UserContext* context = m_P4->m_FileContext ? m_P4->m_FileContext->m_UserContext : nullptr; @@ -856,18 +935,27 @@ void FDepotClient::OnErrorPause(const char* message) void FDepotClient::OnErrorCallback(LogChannel::Enum channel, const char* severity, const char* text) { - if (m_P4->m_OnErrorCallback && *m_P4->m_OnErrorCallback) + if (m_P4->m_OnErrorCallback.get() && *m_P4->m_OnErrorCallback.get()) { - (*m_P4->m_OnErrorCallback)(channel, severity, text); + (*m_P4->m_OnErrorCallback.get())(channel, severity, text); } } void FDepotClient::OnMessageCallback(LogChannel::Enum channel, const char* severity, const char* text) { - if (m_P4->m_OnMessageCallback && *m_P4->m_OnMessageCallback) + if (m_P4->m_OnMessageCallback.get() && *m_P4->m_OnMessageCallback.get()) + { + (*m_P4->m_OnMessageCallback.get())(channel, severity, text); + } +} + +DepotString FDepotClient::OnPromptCallback(const DepotCommand* command, const char* message) +{ + if (command && command->m_Prompt.get() && *command->m_Prompt.get()) { - (*m_P4->m_OnMessageCallback)(channel, severity, text); + return (*command->m_Prompt.get())(message ? message : ""); } + return DepotString(); } LogDevice* FDepotClient::Log() @@ -986,12 +1074,12 @@ WString FDepotClient::GetEnvImpersonatedW(const char* name) const return WString(); } -void FDepotClient::SetErrorCallback(FOnErrorCallback* callback) +void FDepotClient::SetErrorCallback(DepotClientLogCallback callback) { m_P4->m_OnErrorCallback = callback; } -void FDepotClient::SetMessageCallback(FOnMessageCallback* callback) +void FDepotClient::SetMessageCallback(DepotClientLogCallback callback) { m_P4->m_OnMessageCallback = callback; } diff --git a/source/P4VFS.Core/Source/DepotClientCache.cpp b/source/P4VFS.Core/Source/DepotClientCache.cpp index c7d8b3a..42c8404 100644 --- a/source/P4VFS.Core/Source/DepotClientCache.cpp +++ b/source/P4VFS.Core/Source/DepotClientCache.cpp @@ -2,13 +2,13 @@ // Licensed under the MIT license. #include "Pch.h" #include "DepotClientCache.h" +#include "SettingManager.h" namespace Microsoft { namespace P4VFS { namespace P4 { DepotClientCache::DepotClientCache() : - m_KeepAliveSeconds(5 * 60), // 5 minutes m_FreeMap(new FreeMapType) { } @@ -55,7 +55,7 @@ DepotClient DepotClientCache::Alloc(const DepotConfig& config, FileContext& file continue; } - if (client->GetAccessTimeSpan() >= m_KeepAliveSeconds ) + if (client->GetAccessTimeSpan() >= GetIdleTimeoutSeconds()) { if (fileContext.m_LogDevice) { @@ -131,6 +131,11 @@ size_t DepotClientCache::GetFreeCount() const return m_FreeMap->size(); } +time_t DepotClientCache::GetIdleTimeoutSeconds() +{ + return std::max(0, FileCore::SettingManager::StaticInstance().DepotClientCacheIdleTimeoutMs.GetValue()/1000); +} + DepotString DepotClientCache::CreateKey(const DepotConfig& config) { return StringInfo::Format("%s,%s,%s", config.m_Port.c_str(), config.m_User.c_str(), config.m_Client.c_str()); diff --git a/source/P4VFS.Core/Source/DepotOperations.cpp b/source/P4VFS.Core/Source/DepotOperations.cpp index 1632fe0..7a9be45 100644 --- a/source/P4VFS.Core/Source/DepotOperations.cpp +++ b/source/P4VFS.Core/Source/DepotOperations.cpp @@ -869,13 +869,13 @@ DepotOperations::SyncCommand( } } - FDepotClientLogCallback onClientLogCallback = [log](LogChannel::Enum channel, const char* severity, const char* text) -> void + DepotClientLogCallback onClientLogCallback = std::make_shared([log](LogChannel::Enum channel, const char* severity, const char* text) -> void { if (log != nullptr) { log->Write(channel, StringInfo::ToWide(text)); } - }; + }); DepotCommand syncCmd; syncCmd.m_Name = "sync"; @@ -895,8 +895,8 @@ DepotOperations::SyncCommand( syncCmd.m_Flags |= DepotCommand::Flags::UnTagged; } - depotClient->SetMessageCallback(&onClientLogCallback); - depotClient->SetErrorCallback(&onClientLogCallback); + depotClient->SetMessageCallback(onClientLogCallback); + depotClient->SetErrorCallback(onClientLogCallback); DepotResult syncResult = depotClient->Run(syncCmd); depotClient->SetMessageCallback(nullptr); depotClient->SetErrorCallback(nullptr); diff --git a/source/P4VFS.Core/Source/FileCore.cpp b/source/P4VFS.Core/Source/FileCore.cpp index 6e9a5e1..3ff189b 100644 --- a/source/P4VFS.Core/Source/FileCore.cpp +++ b/source/P4VFS.Core/Source/FileCore.cpp @@ -2109,7 +2109,20 @@ Process::ExecuteResult Process::Execute(const wchar_t* cmd, const wchar_t* dir, WString commandLineRw = cmd; if (hUserToken != NULL && hUserToken != INVALID_HANDLE_VALUE) { - if (CreateProcessAsUserW(hUserToken, NULL, &commandLineRw[0], NULL, NULL, TRUE, creationFlags, NULL, dir, &si, &pi) == FALSE) + creationFlags |= CREATE_UNICODE_ENVIRONMENT; + PVOID environment = nullptr; + + if (CreateEnvironmentBlock(&environment, hUserToken, FALSE) == FALSE) + { + result.m_HR = ERROR_INVALID_ENVIRONMENT; + return result; + } + std::shared_ptr environmentScope(nullptr, [environment](void*) + { + DestroyEnvironmentBlock(environment); + }); + + if (CreateProcessAsUserW(hUserToken, NULL, &commandLineRw[0], NULL, NULL, TRUE, creationFlags, environment, dir, &si, &pi) == FALSE) { result.m_HR = HRESULT_FROM_WIN32(GetLastError()); return result; diff --git a/source/P4VFS.CoreInterop/Include/CoreMarshal.h b/source/P4VFS.CoreInterop/Include/CoreMarshal.h index 6fa1c9e..a6fef3e 100644 --- a/source/P4VFS.CoreInterop/Include/CoreMarshal.h +++ b/source/P4VFS.CoreInterop/Include/CoreMarshal.h @@ -139,6 +139,11 @@ class marshal_as_gchandle return System::Runtime::InteropServices::GCHandle::ToIntPtr(m_hGlobal).ToPointer(); } + System::Object^ Target() + { + return m_hGlobal.Target; + } + private: System::Runtime::InteropServices::GCHandle m_hGlobal; }; diff --git a/source/P4VFS.CoreInterop/Include/DepotClientInterop.h b/source/P4VFS.CoreInterop/Include/DepotClientInterop.h index bf8c56a..3d0b09c 100644 --- a/source/P4VFS.CoreInterop/Include/DepotClientInterop.h +++ b/source/P4VFS.CoreInterop/Include/DepotClientInterop.h @@ -30,7 +30,7 @@ public ref class DepotCommand System::String^ Name; array^ Args; System::String^ Input; - System::String^ Prompt; + System::Func^ Prompt; DepotCommandFlags Flags; }; @@ -69,7 +69,7 @@ public ref class DepotClient : public System::IDisposable bool Login( - System::String^ passwd + System::Func^ prompt ); bool diff --git a/source/P4VFS.CoreInterop/Source/DepotClientInterop.cpp b/source/P4VFS.CoreInterop/Source/DepotClientInterop.cpp index c19a995..c5450be 100644 --- a/source/P4VFS.CoreInterop/Source/DepotClientInterop.cpp +++ b/source/P4VFS.CoreInterop/Source/DepotClientInterop.cpp @@ -9,6 +9,30 @@ namespace Microsoft { namespace P4VFS { namespace CoreInterop { +P4::DepotClientPromptCallback marshal_as_prompt_callback(System::Func^ prompt) +{ + if (prompt != nullptr) + { + std::shared_ptr gcprompt = std::make_shared(prompt); + return std::make_shared([gcprompt](const P4::DepotString& message) -> P4::DepotString + { + System::Func^ callback = safe_cast^>(gcprompt->Target()); + if (callback != nullptr) + { + try + { + System::String^ response = callback(gcnew System::String(message.c_str())); + return marshal_as_astring(response); + } + catch (...) + {} + } + return P4::DepotString(); + }); + } + return nullptr; +} + DepotCommand::DepotCommand( ) { @@ -21,7 +45,7 @@ DepotCommand::ToNative( P4::DepotCommand dst; dst.m_Name = marshal_as_astring(Name); dst.m_Input = marshal_as_astring(Input); - dst.m_Prompt = marshal_as_astring(Prompt); + dst.m_Prompt = marshal_as_prompt_callback(Prompt); dst.m_Flags = safe_cast(Flags); dst.m_Args = Marshal::ToNativeAnsi(Args); return dst; @@ -119,11 +143,11 @@ DepotClient::IsLoginRequired( bool DepotClient::Login( - System::String^ passwd + System::Func^ prompt ) { ThrowIfObjectDisposed(); - return m_Data->m_DepotClient->Login(marshal_as_astring(passwd)); + return m_Data->m_DepotClient->Login(marshal_as_prompt_callback(prompt)); } bool diff --git a/source/P4VFS.Driver/Include/DriverVersion.h b/source/P4VFS.Driver/Include/DriverVersion.h index 9499333..010554e 100644 --- a/source/P4VFS.Driver/Include/DriverVersion.h +++ b/source/P4VFS.Driver/Include/DriverVersion.h @@ -3,8 +3,8 @@ #pragma once #define P4VFS_VER_MAJOR 1 // Increment this number almost never -#define P4VFS_VER_MINOR 25 // Increment this number whenever the driver changes -#define P4VFS_VER_BUILD 1 // Increment this number when a major user mode change has been made +#define P4VFS_VER_MINOR 26 // Increment this number whenever the driver changes +#define P4VFS_VER_BUILD 0 // Increment this number when a major user mode change has been made #define P4VFS_VER_REVISION 0 // Increment this number when we rebuild with any change #define P4VFS_VER_STRINGIZE_EX(v) L#v diff --git a/source/P4VFS.Driver/p4vfsflt.inf b/source/P4VFS.Driver/p4vfsflt.inf index cb51bb2..c994fc8 100644 --- a/source/P4VFS.Driver/p4vfsflt.inf +++ b/source/P4VFS.Driver/p4vfsflt.inf @@ -91,5 +91,5 @@ DriverName = "p4vfsflt" DiskId1 = "P4VFS Device Installation Disk" DefaultInstance = "P4VFS Instance" Instance1.Name = "P4VFS Instance" -Instance1.Altitude = "191024" +Instance1.Altitude = "189700" Instance1.Flags = 0x0 ; Allow all attachments diff --git a/source/P4VFS.Service/Source/ServiceHost.cpp b/source/P4VFS.Service/Source/ServiceHost.cpp index 7e20269..afb90b7 100644 --- a/source/P4VFS.Service/Source/ServiceHost.cpp +++ b/source/P4VFS.Service/Source/ServiceHost.cpp @@ -227,8 +227,7 @@ ServiceHost::SrvTickThreadEntry( while (WaitForSingleObject(pSrvHost->m_SrvStopEvent, tickPeriodMs()) != WAIT_OBJECT_0) { - int64_t timeoutSeconds = std::max(1, FileCore::SettingManager::StaticInstance().DepotClientCacheIdleTimeoutMs.GetValue()/1000); - pSrvHost->GarbageCollect(timeoutSeconds); + pSrvHost->GarbageCollect(P4::DepotClientCache::GetIdleTimeoutSeconds()); } return 0; diff --git a/source/P4VFS.UnitTest/Source/UnitTestBase.cs b/source/P4VFS.UnitTest/Source/UnitTestBase.cs index f93e59d..b45f281 100644 --- a/source/P4VFS.UnitTest/Source/UnitTestBase.cs +++ b/source/P4VFS.UnitTest/Source/UnitTestBase.cs @@ -249,6 +249,12 @@ public void WorkspaceReset(DepotConfig config = null) AssertRetry(() => VirtualFileSystem.IsVirtualFileSystemAvailable(), message:"IsVirtualFileSystemAvailable"); ServiceSettings.Reset(); InteractiveOverridePasswd = null; + + if (String.IsNullOrEmpty(config.Passwd)) + { + config.Passwd = UnitTestServer.GetUserP4Passwd(config.User); + Assert(String.IsNullOrEmpty(config.Passwd) == false); + } using (DepotClient depotClient = new DepotClient()) { @@ -548,6 +554,18 @@ public DateTime GetServiceLastRequestTime() return serviceClient.GetServiceStatus()?.LastRequestTime ?? DateTime.MinValue; } + public int GetServiceIdleConnectionCount() + { + return ProcessInfo.ExecuteWaitOutput(P4Exe, String.Format("{0} monitor show -a -l", ClientConfig), echo:true).Lines + .Count(line => Regex.IsMatch(line, @"^\s*(?\d+)\s+(?\w)\s+(?\S+)\s+(?\S+)\s+(IDLE)")); + } + + public bool ServiceGarbageCollect() + { + Extensions.SocketModel.SocketModelClient service = new Extensions.SocketModel.SocketModelClient(); + return service.GarbageCollect(); + } + public FileDifferenceSummary DiffAgainstWorkspace(string fileSpec) { FileDifferenceSummary summary = new FileDifferenceSummary(); diff --git a/source/P4VFS.UnitTest/Source/UnitTestCommon.cs b/source/P4VFS.UnitTest/Source/UnitTestCommon.cs index 11f7b81..b34c7f1 100644 --- a/source/P4VFS.UnitTest/Source/UnitTestCommon.cs +++ b/source/P4VFS.UnitTest/Source/UnitTestCommon.cs @@ -353,6 +353,7 @@ public void DepotConnectionTest() { WorkspaceReset(); string depotPasswd = UnitTestServer.GetUserP4Passwd(_P4User); + string depotSyncPath = "//depot/gears1/Development/Src/Core/..."; string ssoLoginFile = String.Format("{0}\\{1}_ClientLoginSSO.bat", UnitTestServer.GetServerRootFolder(), nameof(DepotConnectionTest)); FileUtilities.DeleteFile(ssoLoginFile); @@ -415,8 +416,23 @@ public void DepotConnectionTest() using (DepotClient depotClient = new DepotClient()) { Assert(depotClient.Connect(_P4Port, _P4Client, _P4User, depotPasswd:depotPasswd)); - Assert(depotClient.Login(depotPasswd)); + Assert(depotClient.Login((_) => depotPasswd)); } + + // Logout and interactive login to sync with the correct password + Assert(p4logout()); + InteractiveOverridePasswd = depotPasswd; + Assert(ProcessInfo.ExecuteWait(P4vfsExe, String.Format("{0} sync {1}", ClientConfig, depotSyncPath), echo:true, log:true) == 0); + InteractiveOverridePasswd = null; + + // Logout and interactive login from service with the correct password + Assert(p4logout()); + InteractiveOverridePasswd = depotPasswd; + Extensions.SocketModel.SocketModelClient service = new Extensions.SocketModel.SocketModelClient(); + Assert(ServiceGarbageCollect()); + Assert(GetServiceIdleConnectionCount() == 0); + Assert(ReconcilePreview(depotSyncPath).Any() == false); + InteractiveOverridePasswd = null; } [TestMethod, Priority(7), TestRemote] @@ -1422,6 +1438,9 @@ public void EnvironmentConfigTest() string clientRoot = GetClientRoot(); string configFileName = ".p4vfsconfig"; + string environmentUser = "northamerica\\quebec"; + string environmentClient = "quebec-pc-depot"; + string environmentPasswd = UnitTestServer.GetUserP4Passwd(environmentUser); string rootFolder = clientRoot; string subFolder = Path.Combine(rootFolder, "depot"); AssertLambda(() => FileUtilities.CreateDirectory(subFolder)); @@ -1433,8 +1452,8 @@ public void EnvironmentConfigTest() depotClient.Unattended = true; // Connect from environment without P4CONFIG Assert(depotClient.Connect(directoryPath:rootFolder)); - Assert(depotClient.ConnectionClient().Client == "quebec-pc-depot"); - Assert(depotClient.Info().UserName == "northamerica\\quebec"); + Assert(depotClient.ConnectionClient().Client == environmentClient); + Assert(depotClient.Info().UserName == environmentUser); Assert(ProcessInfo.ExecuteWaitOutput(P4vfsExe, "info", directory:rootFolder, echo:true, log:true).Lines.Contains("P4 UserName: northamerica\\quebec")); }} }; @@ -1459,9 +1478,9 @@ public void EnvironmentConfigTest() }} }; - using (new SetEnvironmentVariableScope("P4USER", "northamerica\\quebec")) { - using (new SetEnvironmentVariableScope("P4CLIENT", "quebec-pc-depot")) { - using (new SetEnvironmentVariableScope("P4PASSWD", UnitTestServer.DefaultP4Passwd)) { + using (new SetEnvironmentVariableScope("P4USER", environmentUser)) { + using (new SetEnvironmentVariableScope("P4CLIENT", environmentClient)) { + using (new SetEnvironmentVariableScope("P4PASSWD", environmentPasswd)) { using (new SetEnvironmentVariableScope("P4PORT", _P4Port)) { emptyConfigTest(); @@ -1798,11 +1817,6 @@ public void DepotTrustConnectionTest() public void DepotClientCacheIdleTimeoutTest() { WorkspaceReset(); - Func getIdleConnectionCount = () => - { - return ProcessInfo.ExecuteWaitOutput(P4Exe, String.Format("{0} monitor show -a -l", ClientConfig), echo:true).Lines - .Count(line => Regex.IsMatch(line, @"^\s*(?\d+)\s+(?\w)\s+(?\S+)\s+(?\S+)\s+(IDLE)")); - }; Action assertSyncAndReconcile = (service, config) => { @@ -1813,28 +1827,29 @@ public void DepotClientCacheIdleTimeoutTest() // Reconcile the folder using p4.exe. There will be one or more cached service connections Assert(ReconcilePreview(depotFolder).Any() == false); - Assert(getIdleConnectionCount() > 1); + Assert(GetServiceIdleConnectionCount() > 1); }; // There should be no idle connections from any process, including this process - Assert(getIdleConnectionCount() == 0); + Assert(GetServiceIdleConnectionCount() == 0); using (DepotClient depotClient = new DepotClient()) { + Extensions.SocketModel.SocketModelClient service = new Extensions.SocketModel.SocketModelClient(); + // Open a single idle connection Assert(depotClient.Connect(_P4Port, _P4Client, _P4User)); - Assert(getIdleConnectionCount() == 1); - - Extensions.SocketModel.SocketModelClient service = new Extensions.SocketModel.SocketModelClient(); - Assert(service.GarbageCollect()); - Assert(getIdleConnectionCount() == 1); + Assert(GetServiceIdleConnectionCount() == 1); + + Assert(ServiceGarbageCollect()); + Assert(GetServiceIdleConnectionCount() == 1); // Sync and reconcile to open some cached service connections assertSyncAndReconcile(service, depotClient.Config()); - Assert(getIdleConnectionCount() > 1); + Assert(GetServiceIdleConnectionCount() > 1); // Close the idle service connections with a GC - Assert(service.GarbageCollect()); - Assert(getIdleConnectionCount() == 1); + Assert(ServiceGarbageCollect()); + Assert(GetServiceIdleConnectionCount() == 1); // Write a temporary PublicSettingsFilePath settings file with very short GC timeout using (new LocalSettingScope(nameof(SettingManager.GarbageCollectPeriodMs), "100")) { @@ -1846,30 +1861,30 @@ public void DepotClientCacheIdleTimeoutTest() // Restart the service to load our new settings ServiceRestart(); - Assert(getIdleConnectionCount() == 1); + Assert(GetServiceIdleConnectionCount() == 1); Assert(service.GetServiceSetting(nameof(SettingManager.GarbageCollectPeriodMs)).ToInt32() == SettingManager.GarbageCollectPeriodMs); Assert(service.GetServiceSetting(nameof(SettingManager.DepotClientCacheIdleTimeoutMs)).ToInt32() == SettingManager.DepotClientCacheIdleTimeoutMs); // Sync and reconcile to open some cached service connections assertSyncAndReconcile(service, depotClient.Config()); - Assert(getIdleConnectionCount() > 1); + Assert(GetServiceIdleConnectionCount() > 1); // Wait at least DepotClientCacheIdleTimeoutMs for the service connections to be closed - AssertRetry(() => getIdleConnectionCount() == 1, "Waiting for GC", retryWait:500, limitWait:8000); + AssertRetry(() => GetServiceIdleConnectionCount() == 1, "Waiting for GC", retryWait:500, limitWait:8000); }}} // Restart the service to load base settings again Assert(File.Exists(VirtualFileSystem.PublicSettingsFilePath) == false); ServiceRestart(); - Assert(getIdleConnectionCount() == 1); + Assert(GetServiceIdleConnectionCount() == 1); Assert(service.GetServiceSetting(nameof(SettingManager.GarbageCollectPeriodMs)).ToInt32() == SettingManager.GarbageCollectPeriodMs); Assert(service.GetServiceSetting(nameof(SettingManager.DepotClientCacheIdleTimeoutMs)).ToInt32() == SettingManager.DepotClientCacheIdleTimeoutMs); } } - [TestMethod, Priority(33), TestRemote] + [TestMethod, Priority(33)] public void DepotOperationsReconfigTest() { WorkspaceReset(); @@ -1919,15 +1934,15 @@ public void DepotOperationsReconfigTest() string portName = sourceConfig.PortName(); string portNumber = sourceConfig.PortNumber(); Assert(System.Net.IPAddress.TryParse(portName, out System.Net.IPAddress _) == false); - portName = UnitTestServer.GetServerPortIPAddress(portName)?.ToString(); - Assert(System.Net.IPAddress.TryParse(portName, out System.Net.IPAddress _) == true); + string portIP = UnitTestServer.GetServerPortIPAddress(portName)?.ToString(); + Assert(System.Net.IPAddress.TryParse(portIP, out System.Net.IPAddress _) == true); DepotConfig targetConfig = new DepotConfig(); - targetConfig.Port = String.Format("{0}:{1}", portName, portNumber); + targetConfig.Port = String.Format("{0}:{1}", portIP, portNumber); targetConfig.Client = sourceConfig.Client; targetConfig.User = sourceConfig.User; - targetConfig.Passwd = UnitTestServer.GetUserP4Passwd(sourceConfig.User); + InteractiveOverridePasswd = UnitTestServer.GetUserP4Passwd(_P4User); using (DepotClient targetDepotClient = new DepotClient()) { Assert(targetDepotClient.Connect(targetConfig)); @@ -1947,6 +1962,7 @@ public void DepotOperationsReconfigTest() } Assert(ReconcilePreview(clientRoot).Any() == false); + InteractiveOverridePasswd = null; } } } diff --git a/source/P4VFS.UnitTest/Source/UnitTestRemote.cs b/source/P4VFS.UnitTest/Source/UnitTestRemote.cs index dcd9420..69a2770 100644 --- a/source/P4VFS.UnitTest/Source/UnitTestRemote.cs +++ b/source/P4VFS.UnitTest/Source/UnitTestRemote.cs @@ -55,7 +55,7 @@ public void RemoteExecuteTest() Port = String.Format("{0}:{1}", serverPortIPAddress, serverPortNumber), Client = _P4Client, User = _P4User, - Passwd = "Password1", + Passwd = UnitTestServer.GetUserP4Passwd(_P4User) }; Assert(remoteTests.Count > 0); @@ -68,7 +68,7 @@ public void RemoteExecuteTest() private int RemoteExecuteWait(string cmd, bool admin=false, bool shell=false, bool copy=false, StringBuilder stdout=null) { - return ProcessInfo.RemoteExecuteWait(cmd, RemoteHost, RemoteUser, RemotePasswd, admin:admin, shell:shell, copy:copy, stdout:stdout); + return ProcessInfo.RemoteExecuteWait(cmd, RemoteHost, RemoteUser, RemotePasswd, admin:admin, shell:shell, copy:copy, stdout:stdout, interactive:true); } private string GetRequiredEnvironmentVariable(string name) diff --git a/source/P4VFS.UnitTest/Source/UnitTestServer.cs b/source/P4VFS.UnitTest/Source/UnitTestServer.cs index 55e27f4..9316486 100644 --- a/source/P4VFS.UnitTest/Source/UnitTestServer.cs +++ b/source/P4VFS.UnitTest/Source/UnitTestServer.cs @@ -116,7 +116,7 @@ public void StartupLocalPerforceServerTest() DepotResultView userResult = adminClient.Run(new DepotCommand{ Name="user", Args=new[]{"-i", "-f"}, Input=userSpec }).ToView(); Assert(userResult.HasError == false); Assert(adminClient.Users().Nodes.Any(n => n.User == _P4User)); - Assert(adminClient.Run(new DepotCommand{ Name="passwd", Args=new[]{userName}, Prompt=password }).HasError == false); + Assert(adminClient.Run(new DepotCommand{ Name="passwd", Args=new[]{userName}, Prompt=(_) => password }).HasError == false); using (DepotClient userClient = new DepotClient()) { @@ -531,12 +531,15 @@ public static uint DefaultP4PortNumber public static string GetUserP4Passwd(string username) { - return LoadServerRecipeXmlDocument() + string passwd = LoadServerRecipeXmlDocument() .SelectNodes("./server/user") .OfType() .Where(u => u.GetAttribute("name") == username) .Select(u => u.GetAttribute("password")) - .FirstOrDefault() ?? DefaultP4Passwd; + .FirstOrDefault()?.NullIfEmpty() ?? DefaultP4Passwd; + + Assert(String.IsNullOrEmpty(passwd) == false); + return passwd; } public static string GetServerLoginSSOFilePath(string p4Port = null)