diff --git a/GVFS/GVFS.Common/FileSystem/IKernelDriver.cs b/GVFS/GVFS.Common/FileSystem/IKernelDriver.cs index 270019da4..35fbdd1c1 100644 --- a/GVFS/GVFS.Common/FileSystem/IKernelDriver.cs +++ b/GVFS/GVFS.Common/FileSystem/IKernelDriver.cs @@ -1,4 +1,5 @@ using GVFS.Common.Tracing; +using System; namespace GVFS.Common.FileSystem { @@ -8,7 +9,7 @@ public interface IKernelDriver string DriverLogFolderName { get; } bool IsSupported(string normalizedEnlistmentRootPath, out string warning, out string error); string FlushDriverLogs(); - bool TryPrepareFolderForCallbacks(string folderPath, out string error); + bool TryPrepareFolderForCallbacks(string folderPath, out string error, out Exception exception); bool IsReady(JsonTracer tracer, string enlistmentRoot, out string error); } } diff --git a/GVFS/GVFS.Platform.Mac/ProjFSKext.cs b/GVFS/GVFS.Platform.Mac/ProjFSKext.cs index 9adc1d68c..ede5623fc 100644 --- a/GVFS/GVFS.Platform.Mac/ProjFSKext.cs +++ b/GVFS/GVFS.Platform.Mac/ProjFSKext.cs @@ -46,8 +46,9 @@ public bool IsReady(JsonTracer tracer, string enlistmentRoot, out string error) return true; } - public bool TryPrepareFolderForCallbacks(string folderPath, out string error) + public bool TryPrepareFolderForCallbacks(string folderPath, out string error, out Exception exception) { + exception = null; error = string.Empty; Result result = VirtualizationInstance.ConvertDirectoryToVirtualizationRoot(folderPath); if (result != Result.Success) diff --git a/GVFS/GVFS.Platform.Windows/ProjFSFilter.cs b/GVFS/GVFS.Platform.Windows/ProjFSFilter.cs index 544b5678a..3d7b4ce92 100644 --- a/GVFS/GVFS.Platform.Windows/ProjFSFilter.cs +++ b/GVFS/GVFS.Platform.Windows/ProjFSFilter.cs @@ -30,6 +30,7 @@ public class ProjFSFilter : IKernelDriver private const string FilterLoggerSessionName = "Microsoft-Windows-ProjFS-Filter-Log"; private const string ProjFSNativeLibFileName = "ProjectedFSLib.dll"; + private const string ProjFSManagedLibFileName = "ProjectedFSLib.Managed.dll"; private const uint OkResult = 0; private const uint NameCollisionErrorResult = 0x801F0012; @@ -320,18 +321,34 @@ public string FlushDriverLogs() return sb.ToString(); } - public bool TryPrepareFolderForCallbacks(string folderPath, out string error) + public bool TryPrepareFolderForCallbacks(string folderPath, out string error, out Exception exception) { - error = string.Empty; - Guid virtualizationInstanceGuid = Guid.NewGuid(); - HResult result = VirtualizationInstance.ConvertDirectoryToVirtualizationRoot(virtualizationInstanceGuid, folderPath); - if (result != HResult.Ok) + exception = null; + try { - error = "Failed to prepare \"" + folderPath + "\" for callbacks, error: " + result.ToString("F"); - return false; + return this.TryPrepareFolderForCallbacksImpl(folderPath, out error); } + catch (FileNotFoundException e) + { + exception = e; - return true; + if (e.FileName.Equals(ProjFSManagedLibFileName, StringComparison.OrdinalIgnoreCase)) + { + error = $"Failed to load {ProjFSManagedLibFileName}. Ensure that ProjFS is installed and enabled"; + } + else + { + error = $"FileNotFoundException while trying to prepare \"{folderPath}\" for callbacks: {e.Message}"; + } + + return false; + } + catch (Exception e) + { + exception = e; + error = $"Exception while trying to prepare \"{folderPath}\" for callbacks: {e.Message}"; + return false; + } } // TODO 1050199: Once the service is an optional component, GVFS should only attempt to attach @@ -562,7 +579,23 @@ private static EventMetadata CreateEventMetadata(Exception e = null) private static ProcessResult CallPowershellCommand(string command) { return ProcessHelper.Run("powershell.exe", "-NonInteractive -NoProfile -Command \"& { " + command + " }\""); - } + } + + // Using an Impl method allows TryPrepareFolderForCallbacks to catch any ProjFS dependency related exceptions + // thrown in the process of calling this method. + private bool TryPrepareFolderForCallbacksImpl(string folderPath, out string error) + { + error = string.Empty; + Guid virtualizationInstanceGuid = Guid.NewGuid(); + HResult result = VirtualizationInstance.ConvertDirectoryToVirtualizationRoot(virtualizationInstanceGuid, folderPath); + if (result != HResult.Ok) + { + error = "Failed to prepare \"" + folderPath + "\" for callbacks, error: " + result.ToString("F"); + return false; + } + + return true; + } private static class NativeMethods { diff --git a/GVFS/GVFS/CommandLine/CloneVerb.cs b/GVFS/GVFS/CommandLine/CloneVerb.cs index 00a429c07..5d86b909f 100644 --- a/GVFS/GVFS/CommandLine/CloneVerb.cs +++ b/GVFS/GVFS/CommandLine/CloneVerb.cs @@ -609,10 +609,18 @@ private Result CreateClone( } // Prepare the working directory folder for GVFS last to ensure that gvfs mount will fail if gvfs clone has failed + Exception exception; string prepFileSystemError; - if (!GVFSPlatform.Instance.KernelDriver.TryPrepareFolderForCallbacks(enlistment.WorkingDirectoryRoot, out prepFileSystemError)) + if (!GVFSPlatform.Instance.KernelDriver.TryPrepareFolderForCallbacks(enlistment.WorkingDirectoryRoot, out prepFileSystemError, out exception)) { - tracer.RelatedError(prepFileSystemError); + EventMetadata metadata = new EventMetadata(); + metadata.Add(nameof(prepFileSystemError), prepFileSystemError); + if (exception != null) + { + metadata.Add("Exception", exception.ToString()); + } + + tracer.RelatedError(metadata, $"{nameof(this.CreateClone)}: TryPrepareFolderForCallbacks failed"); return new Result(prepFileSystemError); } diff --git a/GVFS/GVFS/CommandLine/DehydrateVerb.cs b/GVFS/GVFS/CommandLine/DehydrateVerb.cs index 71342283a..906745f3c 100644 --- a/GVFS/GVFS/CommandLine/DehydrateVerb.cs +++ b/GVFS/GVFS/CommandLine/DehydrateVerb.cs @@ -221,9 +221,18 @@ private void Mount(ITracer tracer) private void PrepareSrcFolder(ITracer tracer, GVFSEnlistment enlistment) { + Exception exception; string error; - if (!GVFSPlatform.Instance.KernelDriver.TryPrepareFolderForCallbacks(enlistment.WorkingDirectoryRoot, out error)) + if (!GVFSPlatform.Instance.KernelDriver.TryPrepareFolderForCallbacks(enlistment.WorkingDirectoryRoot, out error, out exception)) { + EventMetadata metadata = new EventMetadata(); + metadata.Add(nameof(error), error); + if (exception != null) + { + metadata.Add("Exception", exception.ToString()); + } + + tracer.RelatedError(metadata, $"{nameof(this.PrepareSrcFolder)}: TryPrepareFolderForCallbacks failed"); this.ReportErrorAndExit(tracer, "Failed to recreate the virtualization root: " + error); } }