-
Notifications
You must be signed in to change notification settings - Fork 884
fix: IOException thrown issue from HtmlPostProcessor #10637
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,231 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System.Diagnostics; | ||
| using System.Runtime.InteropServices; | ||
|
|
||
| #nullable enable | ||
|
|
||
| namespace Docfx.Common; | ||
|
|
||
| // Based on https://github.com/dotnet/roslyn/blob/main/src/Compilers/Core/Portable/InternalUtilities/FileLockCheck.cs | ||
|
|
||
| /// <summary> | ||
| /// This class implements checking what processes are locking a file on Windows. | ||
| /// It uses the Restart Manager API to do this. Other platforms are skipped. | ||
| /// </summary> | ||
| internal static class FileLockCheck | ||
| { | ||
| [StructLayout(LayoutKind.Sequential)] | ||
| private struct FILETIME | ||
| { | ||
| public uint dwLowDateTime; | ||
| public uint dwHighDateTime; | ||
| } | ||
|
|
||
| [StructLayout(LayoutKind.Sequential)] | ||
| private struct RM_UNIQUE_PROCESS | ||
| { | ||
| public uint dwProcessId; | ||
| public FILETIME ProcessStartTime; | ||
| } | ||
|
|
||
| [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] | ||
| private struct RM_PROCESS_INFO | ||
| { | ||
| private const int CCH_RM_MAX_APP_NAME = 255; | ||
| private const int CCH_RM_MAX_SVC_NAME = 63; | ||
|
|
||
| internal RM_UNIQUE_PROCESS Process; | ||
| [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_APP_NAME + 1)] | ||
| public string strAppName; | ||
| [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_SVC_NAME + 1)] | ||
| public string strServiceShortName; | ||
| internal int ApplicationType; | ||
| public uint AppStatus; | ||
| public uint TSSessionId; | ||
| [MarshalAs(UnmanagedType.Bool)] | ||
| public bool bRestartable; | ||
| } | ||
|
|
||
| private const string RestartManagerDll = "rstrtmgr.dll"; | ||
|
|
||
| [DllImport(RestartManagerDll, CharSet = CharSet.Unicode)] | ||
| private static extern int RmRegisterResources(uint pSessionHandle, | ||
| uint nFiles, | ||
| string[] rgsFilenames, | ||
| uint nApplications, | ||
| [In] RM_UNIQUE_PROCESS[]? rgApplications, | ||
| uint nServices, | ||
| string[]? rgsServiceNames); | ||
|
|
||
| /// <summary> | ||
| /// Starts a new Restart Manager session. | ||
| /// A maximum of 64 Restart Manager sessions per user session | ||
| /// can be open on the system at the same time. When this | ||
| /// function starts a session, it returns a session handle | ||
| /// and session key that can be used in subsequent calls to | ||
| /// the Restart Manager API. | ||
| /// </summary> | ||
| /// <param name="pSessionHandle"> | ||
| /// A pointer to the handle of a Restart Manager session. | ||
| /// The session handle can be passed in subsequent calls | ||
| /// to the Restart Manager API. | ||
| /// </param> | ||
| /// <param name="dwSessionFlags"> | ||
| /// Reserved. This parameter should be 0. | ||
| /// </param> | ||
| /// <param name="strSessionKey"> | ||
| /// A null-terminated string that contains the session key | ||
| /// to the new session. The string must be allocated before | ||
| /// calling the RmStartSession function. | ||
| /// </param> | ||
| /// <returns>System error codes that are defined in Winerror.h.</returns> | ||
| /// <remarks> | ||
| /// The RmStartSession function doesn't properly null-terminate | ||
| /// the session key, even though the function is documented as | ||
| /// returning a null-terminated string. To work around this bug, | ||
| /// we pre-fill the buffer with null characters so that whatever | ||
| /// ends gets written will have a null terminator (namely, one of | ||
| /// the null characters we placed ahead of time). | ||
| /// <para> | ||
| /// see <see href="http://blogs.msdn.com/b/oldnewthing/archive/2012/02/17/10268840.aspx"/>. | ||
| /// </para> | ||
| /// </remarks> | ||
| [DllImport(RestartManagerDll, CharSet = CharSet.Unicode)] | ||
| private static extern unsafe int RmStartSession( | ||
| out uint pSessionHandle, | ||
| int dwSessionFlags, | ||
| char* strSessionKey); | ||
|
|
||
| /// <summary> | ||
| /// Ends the Restart Manager session. | ||
| /// This function should be called by the primary installer that | ||
| /// has previously started the session by calling the <see cref="RmStartSession"/> | ||
| /// function. The RmEndSession function can be called by a secondary installer | ||
| /// that is joined to the session once no more resources need to be registered | ||
| /// by the secondary installer. | ||
| /// </summary> | ||
| /// <param name="pSessionHandle">A handle to an existing Restart Manager session.</param> | ||
| /// <returns> | ||
| /// The function can return one of the system error codes that are defined in Winerror.h. | ||
| /// </returns> | ||
| [DllImport(RestartManagerDll)] | ||
| private static extern int RmEndSession(uint pSessionHandle); | ||
|
|
||
| [DllImport(RestartManagerDll, CharSet = CharSet.Unicode)] | ||
| private static extern int RmGetList(uint dwSessionHandle, | ||
| out uint pnProcInfoNeeded, | ||
| ref uint pnProcInfo, | ||
| [In, Out] RM_PROCESS_INFO[]? rgAffectedApps, | ||
| ref uint lpdwRebootReasons); | ||
|
|
||
| public static string GetLockingProcessNames(string filePath) | ||
| { | ||
| if (!OperatingSystem.IsWindows()) | ||
| return ""; | ||
|
|
||
| try | ||
| { | ||
| var processes = GetLockingProcessNames([filePath]); | ||
| if (processes.Length == 0) | ||
| return ""; | ||
|
|
||
| var isFileLockedBySelf = processes.Any(x => x.processId == Environment.ProcessId); | ||
| return isFileLockedBySelf | ||
| ? $"File is locked by this process" | ||
| : $"File is locked by external process: {string.Join(',', processes)}"; | ||
| } | ||
| catch (Exception) | ||
| { | ||
| // Never throw if we can't get the processes locking the file. | ||
| return ""; | ||
| } | ||
| } | ||
|
|
||
| private static (int processId, string processName)[] GetLockingProcessNames(string[] paths) | ||
| { | ||
| const int MaxRetries = 6; | ||
| const int ERROR_MORE_DATA = 234; | ||
| const uint RM_REBOOT_REASON_NONE = 0; | ||
|
|
||
| uint handle; | ||
| int res; | ||
|
|
||
| unsafe | ||
| { | ||
| char* key = stackalloc char[sizeof(Guid) * 2 + 1]; | ||
| res = RmStartSession(out handle, 0, key); | ||
| } | ||
|
|
||
| if (res != 0) | ||
| { | ||
| return []; | ||
| } | ||
|
|
||
| try | ||
| { | ||
| res = RmRegisterResources(handle, (uint)paths.Length, paths, 0, null, 0, null); | ||
| if (res != 0) | ||
| { | ||
| return []; | ||
| } | ||
|
|
||
| // | ||
| // Obtain the list of affected applications/services. | ||
| // | ||
| // NOTE: Restart Manager returns the results into the buffer allocated by the caller. The first call to | ||
| // RmGetList() will return the size of the buffer (i.e. nProcInfoNeeded) the caller needs to allocate. | ||
| // The caller then needs to allocate the buffer (i.e. rgAffectedApps) and make another RmGetList() | ||
| // call to ask Restart Manager to write the results into the buffer. However, since Restart Manager | ||
| // refreshes the list every time RmGetList()is called, it is possible that the size returned by the first | ||
| // RmGetList()call is not sufficient to hold the results discovered by the second RmGetList() call. Therefore, | ||
| // it is recommended that the caller follows the following practice to handle this race condition: | ||
| // | ||
| // Use a loop to call RmGetList() in case the buffer allocated according to the size returned in previous | ||
| // call is not enough. | ||
| // | ||
| uint pnProcInfo = 0; | ||
| RM_PROCESS_INFO[]? rgAffectedApps = null; | ||
| int retry = 0; | ||
| do | ||
| { | ||
| uint lpdwRebootReasons = RM_REBOOT_REASON_NONE; | ||
| res = RmGetList(handle, out uint pnProcInfoNeeded, ref pnProcInfo, rgAffectedApps, ref lpdwRebootReasons); | ||
| if (res == 0) | ||
| { | ||
| // If pnProcInfo == 0, then there is simply no locking process (found), in this case rgAffectedApps is "null". | ||
| if (pnProcInfo == 0) | ||
| { | ||
| return []; | ||
| } | ||
|
|
||
| Debug.Assert(rgAffectedApps != null); | ||
|
|
||
| var lockInfos = new List<(int, string)>((int)pnProcInfo); | ||
| for (int i = 0; i < pnProcInfo; i++) | ||
| { | ||
| lockInfos.Add(((int)rgAffectedApps[i].Process.dwProcessId, rgAffectedApps[i].strAppName)); | ||
| } | ||
|
|
||
| return lockInfos.ToArray(); | ||
| } | ||
|
|
||
| if (res != ERROR_MORE_DATA) | ||
| { | ||
| return []; | ||
| } | ||
|
|
||
| pnProcInfo = pnProcInfoNeeded; | ||
| rgAffectedApps = new RM_PROCESS_INFO[pnProcInfo]; | ||
| } | ||
| while (res == ERROR_MORE_DATA && retry++ < MaxRetries); | ||
| } | ||
| finally | ||
| { | ||
| _ = RmEndSession(handle); | ||
| } | ||
|
|
||
| return []; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.