diff --git a/GVFS/GVFS.Common/Http/HttpRequestor.cs b/GVFS/GVFS.Common/Http/HttpRequestor.cs index ca850c9fb..e3c163d42 100644 --- a/GVFS/GVFS.Common/Http/HttpRequestor.cs +++ b/GVFS/GVFS.Common/Http/HttpRequestor.cs @@ -8,6 +8,7 @@ using System.Net; using System.Net.Http; using System.Net.Http.Headers; +using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -27,9 +28,15 @@ public abstract class HttpRequestor : IDisposable static HttpRequestor() { - ServicePointManager.SecurityProtocol = ServicePointManager.SecurityProtocol | SecurityProtocolType.Tls12; - ServicePointManager.DefaultConnectionLimit = Environment.ProcessorCount; - availableConnections = new SemaphoreSlim(ServicePointManager.DefaultConnectionLimit); + /* If machine.config is locked, then initializing ServicePointManager will fail and be unrecoverable. + * Machine.config locking is typically very brief (~1ms by the antivirus scanner) so we can attempt to lock + * it ourselves (by opening it for read) *beforehand and briefly wait if it's locked */ + using (var machineConfigLock = GetMachineConfigLock()) + { + ServicePointManager.SecurityProtocol = ServicePointManager.SecurityProtocol | SecurityProtocolType.Tls12; + ServicePointManager.DefaultConnectionLimit = Environment.ProcessorCount; + availableConnections = new SemaphoreSlim(ServicePointManager.DefaultConnectionLimit); + } } protected HttpRequestor(ITracer tracer, RetryConfig retryConfig, Enlistment enlistment) @@ -329,5 +336,28 @@ private static bool TryGetResponseMessageFromHttpRequestException(HttpRequestExc return true; } + + private static FileStream GetMachineConfigLock() + { + var machineConfigLocation = RuntimeEnvironment.SystemConfigurationFile; + var tries = 0; + var maxTries = 3; + while (tries++ < maxTries) + { + try + { + /* Opening with FileShare.Read will fail if another process (eg antivirus) has opened the file for write, + but will still let ServicePointManager read the file.*/ + FileStream stream = File.Open(machineConfigLocation, FileMode.Open, FileAccess.Read, FileShare.Read); + return stream; + } + catch (IOException e) when ((uint)e.HResult == 0x80070020) // SHARING_VIOLATION + { + Thread.Sleep(10); + } + } + /* Couldn't get the lock - the process will likely fail. */ + return null; + } } } diff --git a/GVFS/GVFS.Installers/GVFS.Installers.csproj b/GVFS/GVFS.Installers/GVFS.Installers.csproj index 44c166d56..bf0c5ec7f 100644 --- a/GVFS/GVFS.Installers/GVFS.Installers.csproj +++ b/GVFS/GVFS.Installers/GVFS.Installers.csproj @@ -12,7 +12,7 @@ - + diff --git a/GVFS/GVFS.Installers/Setup.iss b/GVFS/GVFS.Installers/Setup.iss index 109f4f45b..886da1042 100644 --- a/GVFS/GVFS.Installers/Setup.iss +++ b/GVFS/GVFS.Installers/Setup.iss @@ -39,8 +39,8 @@ MinVersion=10.0.14374 DisableDirPage=yes DisableReadyPage=yes SetupIconFile="{#LayoutDir}\GitVirtualFileSystem.ico" -ArchitecturesInstallIn64BitMode=x64 -ArchitecturesAllowed=x64 +ArchitecturesInstallIn64BitMode=x64compatible +ArchitecturesAllowed=x64compatible WizardImageStretch=no WindowResizable=no CloseApplications=yes diff --git a/GVFS/GitHooksLoader/GitHooksLoader.cpp b/GVFS/GitHooksLoader/GitHooksLoader.cpp index d86638ef8..d38193280 100644 --- a/GVFS/GitHooksLoader/GitHooksLoader.cpp +++ b/GVFS/GitHooksLoader/GitHooksLoader.cpp @@ -117,6 +117,18 @@ int ExecuteHook(const std::wstring &applicationName, wchar_t *hookName, int argc si.dwFlags = STARTF_USESTDHANDLES; ZeroMemory(&pi, sizeof(pi)); + + /* The child process will inherit ErrorMode from this process. + * SEM_FAILCRITICALERRORS will prevent the .NET runtime from + * creating a dialog box for critical errors - in particular + * if antivirus has locked the machine.config file. + * Disabling the dialog box lets the child process (typically GVFS.Hooks.exe) + * continue trying to run, and if it still needs machine.config then it + * can handle the exception at that time (whereas the dialog box would + * hang the app until clicked, and is not handleable by our code). + */ + UINT previousErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS); + if (!CreateProcess( NULL, // Application name const_cast(commandLine.c_str()), @@ -131,8 +143,10 @@ int ExecuteHook(const std::wstring &applicationName, wchar_t *hookName, int argc ) { fwprintf(stderr, L"Could not execute '%s'. CreateProcess error (%d).\n", applicationName.c_str(), GetLastError()); + SetErrorMode(previousErrorMode); exit(3); } + SetErrorMode(previousErrorMode); // Wait until child process exits. WaitForSingleObject(pi.hProcess, INFINITE); diff --git a/scripts/Build.bat b/scripts/Build.bat index 637b1e18e..cf8ebefd1 100644 --- a/scripts/Build.bat +++ b/scripts/Build.bat @@ -43,7 +43,7 @@ IF NOT EXIST "%NUGET_EXEC%" ( REM Acquire vswhere to find VS installations reliably SET VSWHERE_VER=2.6.7 -"%NUGET_EXEC%" install vswhere -Version %VSWHERE_VER% || exit /b 1 +"%NUGET_EXEC%" install vswhere -Version %VSWHERE_VER% -OutputDirectory %VFS_PACKAGESDIR% || exit /b 1 SET VSWHERE_EXEC="%VFS_PACKAGESDIR%\vswhere.%VSWHERE_VER%\tools\vswhere.exe" REM Assumes default installation location for Windows 10 SDKs