uwekeim

Who Is Locking This?

May 24th, 2022 (edited)
353
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 10.23 KB | None | 0 0
  1. using System.IO;
  2. using System.Runtime.InteropServices;
  3.  
  4. public static class FileLockHelper
  5. {
  6.     // http://stackoverflow.com/questions/1304/how-to-check-for-file-lock-in-c
  7.  
  8.     private const int NumberOfTries = 3;
  9.     private const int TimeIntervalBetweenTries = 1000;
  10.  
  11.     public static bool IsFileLocked(string fileName)
  12.     {
  13.         if (File.Exists(fileName))
  14.         {
  15.             try
  16.             {
  17.                 var tries = 0;
  18.                 while (true)
  19.                 {
  20.                     try
  21.                     {
  22.                         using (File.Open(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
  23.                         {
  24.                             return false;
  25.                         }
  26.                     }
  27.                     catch (IOException e)
  28.                     {
  29.                         LogCentral.Current.Error(e,
  30.                             $@"{nameof(FileLockHelper)}.{nameof(IsFileLocked)}: {nameof(IOException)} bei Datei „{fileName}“.");
  31.  
  32.                         if (!IsFileLocked(e))
  33.                             throw;
  34.                         if (++tries > NumberOfTries)
  35.                         {
  36.                             logLockingProcess(fileName);
  37.                             return true;
  38.                         }
  39.  
  40.                         Thread.Sleep(TimeIntervalBetweenTries);
  41.                     }
  42.                     catch (UnauthorizedAccessException e)
  43.                     {
  44.                         LogCentral.Current.Error(e,
  45.                             $@"{nameof(FileLockHelper)}.{nameof(IsFileLocked)}: {
  46.                                nameof(UnauthorizedAccessException)
  47.                            } bei Datei „{fileName}“.");
  48.  
  49.                         if (!IsFileLocked(e))
  50.                             throw;
  51.                         if (++tries > NumberOfTries)
  52.                         {
  53.                             logLockingProcess(fileName);
  54.                             return true;
  55.                         }
  56.                         Thread.Sleep(TimeIntervalBetweenTries);
  57.                     }
  58.                 }
  59.             }
  60.             catch (Exception e)
  61.             {
  62.                 // 2017-04-27, Uwe Keim:
  63.                 // Nie throwen.
  64.  
  65.                 LogCentral.Current.Error(e,
  66.                     $@"{nameof(FileLockHelper)}.{nameof(IsFileLocked)}: {e.GetType()} bei Datei „{fileName}“.");
  67.  
  68.                 return false;
  69.             }
  70.         }
  71.         else
  72.         {
  73.             return false;
  74.         }
  75.     }
  76.  
  77.     private static void logLockingProcess(string fileName)
  78.     {
  79.         LogCentral.Current.Info($@"File „{fileName}“ is locked by process „{WhoIsLocking(fileName, true).Message}“.");
  80.     }
  81.  
  82.     private static bool IsFileLocked(Exception exception)
  83.     {
  84.         var errorCode = Marshal.GetHRForException(exception) & ((1 << 16) - 1);
  85.         return errorCode is 32 or 33;
  86.     }
  87.  
  88.     /// <summary>
  89.     /// Find out what process(es) have a lock on the specified file.
  90.     /// </summary>
  91.     /// <param name="path">Path of the file.</param>
  92.     /// <param name="catchExceptions">Whether to return empty/partially filled list upon errors.</param>
  93.     /// <returns>Processes locking the file</returns>
  94.     /// <remarks>See also:
  95.     /// https://stackoverflow.com/a/20623311/107625
  96.     /// http://msdn.microsoft.com/en-us/library/windows/desktop/aa373661(v=vs.85).aspx
  97.     /// http://wyupdate.googlecode.com/svn-history/r401/trunk/frmFilesInUse.cs (no copyright in code at time of viewing)
  98.     ///
  99.     /// </remarks>
  100.     public static WhoIsLockingResult WhoIsLocking(string path, bool catchExceptions)
  101.     {
  102.         var key = Guid.NewGuid().ToString();
  103.         var processes = new List<Process>();
  104.  
  105.         var res = RmStartSession(out var handle, 0, key);
  106.         if (res != 0) throw new Exception("Could not begin restart session.  Unable to determine file locker.");
  107.  
  108.         try
  109.         {
  110.             const int ERROR_MORE_DATA = 234;
  111.             uint pnProcInfo = 0,
  112.                 lpdwRebootReasons = RmRebootReasonNone;
  113.  
  114.             var resources = new[] { path }; // Just checking on one resource.
  115.  
  116.             res = RmRegisterResources(handle, (uint)resources.Length, resources, 0, null, 0, null);
  117.  
  118.             if (res != 0) throw new Exception("Could not register resource.");
  119.  
  120.             //Note: there's a race condition here -- the first call to RmGetList() returns
  121.             //      the total number of process. However, when we call RmGetList() again to get
  122.             //      the actual processes this number may have increased.
  123.             res = RmGetList(handle, out var pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons);
  124.  
  125.             if (res == ERROR_MORE_DATA)
  126.             {
  127.                 // Create an array to store the process results
  128.                 var processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded];
  129.                 pnProcInfo = pnProcInfoNeeded;
  130.  
  131.                 // Get the list
  132.                 res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons);
  133.                 if (res == 0)
  134.                 {
  135.                     processes = new List<Process>((int)pnProcInfo);
  136.  
  137.                     // Enumerate all of the results and add them to the
  138.                     // list to be returned
  139.                     for (var i = 0; i < pnProcInfo; i++)
  140.                     {
  141.                         try
  142.                         {
  143.                             processes.Add(Process.GetProcessById(processInfo[i].Process.dwProcessId));
  144.                         }
  145.                         // catch the error -- in case the process is no longer running
  146.                         catch (ArgumentException)
  147.                         {
  148.                         }
  149.                     }
  150.                 }
  151.                 else throw new Exception("Could not list processes locking resource.");
  152.             }
  153.             else if (res != 0)
  154.                 throw new Exception("Could not list processes locking resource. Failed to get size of result.");
  155.         }
  156.         catch (Exception x)
  157.         {
  158.             if (catchExceptions)
  159.             {
  160.                 LogCentral.Current.Warn(x, $@"Ignoring exception while checking who is locking file „{path}“.");
  161.             }
  162.             else
  163.             {
  164.                 throw;
  165.             }
  166.         }
  167.         finally
  168.         {
  169.             RmEndSession(handle);
  170.         }
  171.  
  172.         return new WhoIsLockingResult(processes);
  173.     }
  174.  
  175.     [StructLayout(LayoutKind.Sequential)]
  176.     private struct RM_UNIQUE_PROCESS
  177.     {
  178.         // ReSharper disable FieldCanBeMadeReadOnly.Local
  179.         // ReSharper disable MemberCanBePrivate.Local
  180.         public int dwProcessId;
  181.         public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime;
  182.         // ReSharper restore MemberCanBePrivate.Local
  183.         // ReSharper restore FieldCanBeMadeReadOnly.Local
  184.     }
  185.  
  186.     private const int RmRebootReasonNone = 0;
  187.     private const int CCH_RM_MAX_APP_NAME = 255;
  188.     private const int CCH_RM_MAX_SVC_NAME = 63;
  189.  
  190.     private enum RM_APP_TYPE
  191.     {
  192.         // ReSharper disable UnusedMember.Local
  193.         RmUnknownApp = 0,
  194.         RmMainWindow = 1,
  195.         RmOtherWindow = 2,
  196.         RmService = 3,
  197.         RmExplorer = 4,
  198.         RmConsole = 5,
  199.         RmCritical = 1000
  200.         // ReSharper restore UnusedMember.Local
  201.     }
  202.  
  203.     [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
  204.     private struct RM_PROCESS_INFO
  205.     {
  206.         // ReSharper disable FieldCanBeMadeReadOnly.Local
  207.         // ReSharper disable MemberCanBePrivate.Local
  208.         public RM_UNIQUE_PROCESS Process;
  209.  
  210.         [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_APP_NAME + 1)]
  211.         public string strAppName;
  212.  
  213.         [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_SVC_NAME + 1)]
  214.         public string strServiceShortName;
  215.  
  216.         public RM_APP_TYPE ApplicationType;
  217.         public uint AppStatus;
  218.         public uint TSSessionId;
  219.         [MarshalAs(UnmanagedType.Bool)] public bool bRestartable;
  220.         // ReSharper restore MemberCanBePrivate.Local
  221.         // ReSharper restore FieldCanBeMadeReadOnly.Local
  222.     }
  223.  
  224.     [DllImport(@"rstrtmgr.dll", CharSet = CharSet.Unicode)]
  225.     private static extern int RmRegisterResources(uint pSessionHandle,
  226.         uint nFiles,
  227.         string[] rgsFilenames,
  228.         uint nApplications,
  229.         [In] RM_UNIQUE_PROCESS[] rgApplications,
  230.         uint nServices,
  231.         string[] rgsServiceNames);
  232.  
  233.     [DllImport(@"rstrtmgr.dll", CharSet = CharSet.Auto)]
  234.     private static extern int RmStartSession(out uint pSessionHandle, int dwSessionFlags, string strSessionKey);
  235.  
  236.     [DllImport(@"rstrtmgr.dll")]
  237.     private static extern int RmEndSession(uint pSessionHandle);
  238.  
  239.     [DllImport(@"rstrtmgr.dll")]
  240.     private static extern int RmGetList(uint dwSessionHandle,
  241.         out uint pnProcInfoNeeded,
  242.         ref uint pnProcInfo,
  243.         [In, Out] RM_PROCESS_INFO[] rgAffectedApps,
  244.         ref uint lpdwRebootReasons);
  245. }
  246.  
  247. public sealed class WhoIsLockingResult
  248. {
  249.     public List<Process> Processes { get; }
  250.  
  251.     internal WhoIsLockingResult(List<Process> processes)
  252.     {
  253.         Processes = processes;
  254.     }
  255.  
  256.     public string Message
  257.     {
  258.         get
  259.         {
  260.             if (Processes is not {Count: > 0}) return string.Empty;
  261.  
  262.             var result = new StringBuilder();
  263.  
  264.             foreach (var process in Processes)
  265.             {
  266.                 if (result.Length > 0) result.Append(@", ");
  267.  
  268.                 string name;
  269.  
  270.                 // Von https://stackoverflow.com/a/5497123/107625:
  271.                 //
  272.                 // "There is one catch with this API, if you are running this code in
  273.                 // 32 bit application, you'll not be able to access 64-bit application paths."
  274.                 //
  275.                 // Deshalb hier abfangen.
  276.                 try
  277.                 {
  278.                     name = process.MainModule?.FileName ?? string.Empty;
  279.                 }
  280.                 catch (Exception)
  281.                 {
  282.                     name = process.ProcessName;
  283.                 }
  284.  
  285.                 result.Append(name);
  286.             }
  287.  
  288.             return result.ToString();
  289.         }
  290.     }
  291. }
Add Comment
Please, Sign In to add comment