Przeglądaj źródła

Fix shadow copy CheckUpToDate to detect subdirectory DLL changes (#65109)

Aditya Mandaleeka 1 miesiąc temu
rodzic
commit
338a525818

+ 10 - 6
src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.cpp

@@ -231,24 +231,28 @@ bool Environment::CheckUpToDate(const std::wstring& source, const std::filesyste
             auto sourceFile = path.path().filename();
             auto destinationPath = (destination / sourceFile);
 
+            // Only compare timestamps if destination exists
             if (std::filesystem::directory_entry(destinationPath).exists())
             {
                 auto originalFileTime = std::filesystem::last_write_time(path);
                 auto destFileTime = std::filesystem::last_write_time(destinationPath);
-                if (originalFileTime > destFileTime) // file write time is the same
+                if (originalFileTime > destFileTime)
                 {
                     return false;
                 }
             }
-
-            CopyFile(path.path().wstring().c_str(), destinationPath.wstring().c_str(), FALSE);
         }
         else if (path.is_directory())
         {
-            auto sourceInnerDirectory = std::filesystem::directory_entry(path);
-            if (sourceInnerDirectory.path() != directoryToIgnore)
+            auto sourceInnerDirectory = path.path();
+            // Use prefix match to skip the shadow copy directory and its subdirectories
+            if (sourceInnerDirectory.wstring().rfind(directoryToIgnore, 0) != 0)
             {
-                CheckUpToDate(/* source */ path.path(), /* destination */ destination / path.path().filename(), extension, directoryToIgnore);
+                // Propagate result from subdirectories
+                if (!CheckUpToDate(/* source */ path.path(), /* destination */ destination / path.path().filename(), extension, directoryToIgnore))
+                {
+                    return false;
+                }
             }
         }
     }

+ 6 - 2
src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp

@@ -438,13 +438,17 @@ FILE_WATCHER::RunNotificationCallback(
 HRESULT
 FILE_WATCHER::Monitor(VOID)
 {
-    DWORD   cbRead;
+    DWORD   cbRead = 0;
     ZeroMemory(&_overlapped, sizeof(_overlapped));
 
+    // Watch subdirectories when shadow copy is enabled to detect DLL changes in nested folders.
+    // For app_offline.htm monitoring only, subdirectory watching is not needed.
+    BOOL watchSubtree = m_fShadowCopyEnabled ? TRUE : FALSE;
+
     RETURN_LAST_ERROR_IF(!ReadDirectoryChangesW(_hDirectory,
         _buffDirectoryChanges.QueryPtr(),
         _buffDirectoryChanges.QuerySize(),
-        FALSE,        // Watching sub dirs. Set to False now as only monitoring app_offline
+        watchSubtree,
         FILE_NOTIFY_VALID_MASK & ~FILE_NOTIFY_CHANGE_LAST_ACCESS & ~FILE_NOTIFY_CHANGE_SECURITY & ~FILE_NOTIFY_CHANGE_ATTRIBUTES,
         &cbRead,
         &_overlapped,

+ 39 - 0
src/Servers/IIS/IIS/test/IIS.ShadowCopy.Tests/ShadowCopyTests.cs

@@ -137,6 +137,45 @@ public class ShadowCopyTests : IISFunctionalTestBase
         Assert.True(response.IsSuccessStatusCode);
     }
 
+    [ConditionalFact]
+    public async Task ShadowCopyDetectsSubdirectoryDllChange()
+    {
+        using var directory = TempDirectory.Create();
+        var deploymentParameters = Fixture.GetBaseDeploymentParameters();
+        deploymentParameters.HandlerSettings["enableShadowCopy"] = "true";
+        deploymentParameters.HandlerSettings["shadowCopyDirectory"] = directory.DirectoryPath;
+
+        var deploymentResult = await DeployAsync(deploymentParameters);
+
+        var response = await deploymentResult.HttpClient.GetAsync("Wow!");
+        Assert.True(response.IsSuccessStatusCode);
+
+        // Find a DLL in a subdirectory and touch it to trigger change detection
+        var contentRoot = new DirectoryInfo(deploymentResult.ContentRoot);
+        string dllPath = null;
+
+        foreach (var subDir in contentRoot.GetDirectories())
+        {
+            var dll = subDir.GetFiles("*.dll", SearchOption.AllDirectories).FirstOrDefault();
+            if (dll is not null)
+            {
+                dllPath = dll.FullName;
+                break;
+            }
+        }
+
+        Assert.NotNull(dllPath);
+
+        // Rewrite the file to update its timestamp
+        var fileContents = File.ReadAllBytes(dllPath);
+        File.WriteAllBytes(dllPath, fileContents);
+
+        await deploymentResult.AssertRecycledAsync();
+
+        response = await deploymentResult.HttpClient.GetAsync("Wow!");
+        Assert.True(response.IsSuccessStatusCode);
+    }
+
     [ConditionalFact]
     public async Task ShadowCopyDeleteFolderDuringShutdownWorks()
     {