uwekeim icon

Who Is Locking This?

uwekeim | PRO | 05/24/22 04:15:42 PM UTC (Edited) | 0 ⭐ | 764 👁️ | Never ⏰ | []
C# |

10.23 KB

|

None

|

0 👍

/

0 👎

using System.IO;
using System.Runtime.InteropServices;
 
public static class FileLockHelper
{
    // http://stackoverflow.com/questions/1304/how-to-check-for-file-lock-in-c
 
    private const int NumberOfTries = 3;
    private const int TimeIntervalBetweenTries = 1000;
 
    public static bool IsFileLocked(string fileName)
    {
        if (File.Exists(fileName))
        {
            try
            {
                var tries = 0;
                while (true)
                {
                    try
                    {
                        using (File.Open(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
                        {
                            return false;
                        }
                    }
                    catch (IOException e)
                    {
                        LogCentral.Current.Error(e,
                            $@"{nameof(FileLockHelper)}.{nameof(IsFileLocked)}: {nameof(IOException)} bei Datei „{fileName}“.");
 
                        if (!IsFileLocked(e))
                            throw;
                        if (++tries > NumberOfTries)
                        {
                            logLockingProcess(fileName);
                            return true;
                        }
 
                        Thread.Sleep(TimeIntervalBetweenTries);
                    }
                    catch (UnauthorizedAccessException e)
                    {
                        LogCentral.Current.Error(e,
                            $@"{nameof(FileLockHelper)}.{nameof(IsFileLocked)}: {
                                nameof(UnauthorizedAccessException)
                            } bei Datei „{fileName}“.");
 
                        if (!IsFileLocked(e))
                            throw;
                        if (++tries > NumberOfTries)
                        {
                            logLockingProcess(fileName);
                            return true;
                        }
                        Thread.Sleep(TimeIntervalBetweenTries);
                    }
                }
            }
            catch (Exception e)
            {
                // 2017-04-27, Uwe Keim:
                // Nie throwen.
 
                LogCentral.Current.Error(e,
                    $@"{nameof(FileLockHelper)}.{nameof(IsFileLocked)}: {e.GetType()} bei Datei „{fileName}“.");
 
                return false;
            }
        }
        else
        {
            return false;
        }
    }
 
    private static void logLockingProcess(string fileName)
    {
        LogCentral.Current.Info($@"File „{fileName}“ is locked by process „{WhoIsLocking(fileName, true).Message}“.");
    }
 
    private static bool IsFileLocked(Exception exception)
    {
        var errorCode = Marshal.GetHRForException(exception) & ((1 << 16) - 1);
        return errorCode is 32 or 33;
    }
 
    /// <summary>
    /// Find out what process(es) have a lock on the specified file.
    /// </summary>
    /// <param name="path">Path of the file.</param>
    /// <param name="catchExceptions">Whether to return empty/partially filled list upon errors.</param>
    /// <returns>Processes locking the file</returns>
    /// <remarks>See also:
    /// https://stackoverflow.com/a/20623311/107625
    /// http://msdn.microsoft.com/en-us/library/windows/desktop/aa373661(v=vs.85).aspx
    /// http://wyupdate.googlecode.com/svn-history/r401/trunk/frmFilesInUse.cs (no copyright in code at time of viewing)
    /// 
    /// </remarks>
    public static WhoIsLockingResult WhoIsLocking(string path, bool catchExceptions)
    {
        var key = Guid.NewGuid().ToString();
        var processes = new List<Process>();
 
        var res = RmStartSession(out var handle, 0, key);
        if (res != 0) throw new Exception("Could not begin restart session.  Unable to determine file locker.");
 
        try
        {
            const int ERROR_MORE_DATA = 234;
            uint pnProcInfo = 0,
                lpdwRebootReasons = RmRebootReasonNone;
 
            var resources = new[] { path }; // Just checking on one resource.
 
            res = RmRegisterResources(handle, (uint)resources.Length, resources, 0, null, 0, null);
 
            if (res != 0) throw new Exception("Could not register resource.");
 
            //Note: there's a race condition here -- the first call to RmGetList() returns
            //      the total number of process. However, when we call RmGetList() again to get
            //      the actual processes this number may have increased.
            res = RmGetList(handle, out var pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons);
 
            if (res == ERROR_MORE_DATA)
            {
                // Create an array to store the process results
                var processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded];
                pnProcInfo = pnProcInfoNeeded;
 
                // Get the list
                res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons);
                if (res == 0)
                {
                    processes = new List<Process>((int)pnProcInfo);
 
                    // Enumerate all of the results and add them to the 
                    // list to be returned
                    for (var i = 0; i < pnProcInfo; i++)
                    {
                        try
                        {
                            processes.Add(Process.GetProcessById(processInfo[i].Process.dwProcessId));
                        }
                        // catch the error -- in case the process is no longer running
                        catch (ArgumentException)
                        {
                        }
                    }
                }
                else throw new Exception("Could not list processes locking resource.");
            }
            else if (res != 0)
                throw new Exception("Could not list processes locking resource. Failed to get size of result.");
        }
        catch (Exception x)
        {
            if (catchExceptions)
            {
                LogCentral.Current.Warn(x, $@"Ignoring exception while checking who is locking file „{path}“.");
            }
            else
            {
                throw;
            }
        }
        finally
        {
            RmEndSession(handle);
        }
 
        return new WhoIsLockingResult(processes);
    }
 
    [StructLayout(LayoutKind.Sequential)]
    private struct RM_UNIQUE_PROCESS
    {
        // ReSharper disable FieldCanBeMadeReadOnly.Local
        // ReSharper disable MemberCanBePrivate.Local
        public int dwProcessId;
        public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime;
        // ReSharper restore MemberCanBePrivate.Local
        // ReSharper restore FieldCanBeMadeReadOnly.Local
    }
 
    private const int RmRebootReasonNone = 0;
    private const int CCH_RM_MAX_APP_NAME = 255;
    private const int CCH_RM_MAX_SVC_NAME = 63;
 
    private enum RM_APP_TYPE
    {
        // ReSharper disable UnusedMember.Local
        RmUnknownApp = 0,
        RmMainWindow = 1,
        RmOtherWindow = 2,
        RmService = 3,
        RmExplorer = 4,
        RmConsole = 5,
        RmCritical = 1000
        // ReSharper restore UnusedMember.Local
    }
 
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    private struct RM_PROCESS_INFO
    {
        // ReSharper disable FieldCanBeMadeReadOnly.Local
        // ReSharper disable MemberCanBePrivate.Local
        public 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;
 
        public RM_APP_TYPE ApplicationType;
        public uint AppStatus;
        public uint TSSessionId;
        [MarshalAs(UnmanagedType.Bool)] public bool bRestartable;
        // ReSharper restore MemberCanBePrivate.Local
        // ReSharper restore FieldCanBeMadeReadOnly.Local
    }
 
    [DllImport(@"rstrtmgr.dll", 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);
 
    [DllImport(@"rstrtmgr.dll", CharSet = CharSet.Auto)]
    private static extern int RmStartSession(out uint pSessionHandle, int dwSessionFlags, string strSessionKey);
 
    [DllImport(@"rstrtmgr.dll")]
    private static extern int RmEndSession(uint pSessionHandle);
 
    [DllImport(@"rstrtmgr.dll")]
    private static extern int RmGetList(uint dwSessionHandle,
        out uint pnProcInfoNeeded,
        ref uint pnProcInfo,
        [In, Out] RM_PROCESS_INFO[] rgAffectedApps,
        ref uint lpdwRebootReasons);
}
 
public sealed class WhoIsLockingResult
{
    public List<Process> Processes { get; }
 
    internal WhoIsLockingResult(List<Process> processes)
    {
        Processes = processes;
    }
 
    public string Message
    {
        get
        {
            if (Processes is not {Count: > 0}) return string.Empty;
 
            var result = new StringBuilder();
 
            foreach (var process in Processes)
            {
                if (result.Length > 0) result.Append(@", ");
 
                string name;
 
                // Von https://stackoverflow.com/a/5497123/107625:
                //
                // "There is one catch with this API, if you are running this code in
                // 32 bit application, you'll not be able to access 64-bit application paths."
                //
                // Deshalb hier abfangen.
                try
                {
                    name = process.MainModule?.FileName ?? string.Empty;
                }
                catch (Exception)
                {
                    name = process.ProcessName;
                }
 
                result.Append(name);
            }
 
            return result.ToString();
        }
    }
}

Comments