【问题标题】:RmGetList() API fails when file is locked using Ez file locker but works with another file locking utility当使用 Ez 文件锁定器锁定文件但与另一个文件锁定实用程序一起使用时,RmGetList() API 失败
【发布时间】:2016-12-27 16:13:46
【问题描述】:

我在使用 Restart Manager API:RmGetlist() 时遇到了一个奇怪的问题。 为了模拟文件锁定场景,我正在使用以下第 3 方文件锁定实用程序:

Ez 文件柜 -http://www.xoslab.com/efl.html -

文件柜 http://www.jensscheffler.de/filelocker

这里的奇怪问题是,这两个实用程序都锁定了某个文件,但是,RMGetList() 使用第一个文件锁定实用程序(Ez File lock)失败并出现访问被拒绝错误(5),而它与第二个文件一起使用锁定实用程序。

这里可能有什么问题? 为什么 RmGetList() 会在一个文件锁定实用程序上失败,而在另一个文件锁定实用程序上工作?

下面是正在使用的代码:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security;
using System.IO;
using System.Windows.Forms;

namespace RMSession
{
    class Program
    {


        public static void GetProcessesUsingFiles(string[] filePaths)
        {
            uint sessionHandle;
            int error = NativeMethods.RmStartSession(out sessionHandle, 0, Guid.NewGuid().ToString("N"));
            if (error == 0)
            {
                try
                {
                    error = NativeMethods.RmRegisterResources(sessionHandle, (uint)filePaths.Length, filePaths, 0, null, 0, null);
                    if (error == 0)
                    {
                        RM_PROCESS_INFO[] processInfo = null;
                        uint pnProcInfoNeeded = 0, pnProcInfo = 0, lpdwRebootReasons = RmRebootReasonNone;
                        error = NativeMethods.RmGetList(sessionHandle, out pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons);
                        while (error == ERROR_MORE_DATA)
                        {
                            processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded];
                            pnProcInfo = (uint)processInfo.Length;
                            error = NativeMethods.RmGetList(sessionHandle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons);
                        }

                        if (error == 0 && processInfo != null)
                        {
                            for (var i = 0; i < pnProcInfo; i++)
                            {
                                RM_PROCESS_INFO procInfo = processInfo[i];
                                Process proc = null;
                                try
                                {
                                    proc = Process.GetProcessById(procInfo.Process.dwProcessId);
                                }
                                catch (ArgumentException)
                                {
                                    // Eat exceptions for processes which are no longer running.
                                }

                                if (proc != null)
                                {
                                    //yield return proc;
                                }
                            }
                        }
                    }
                }
                finally
                {
                    NativeMethods.RmEndSession(sessionHandle);
                }
            }
        }

        private const int RmRebootReasonNone = 0;
        private const int CCH_RM_MAX_APP_NAME = 255;
        private const int CCH_RM_MAX_SVC_NAME = 63;
        private const int ERROR_MORE_DATA = 234;

        [StructLayout(LayoutKind.Sequential)]
        private struct RM_UNIQUE_PROCESS
        {
            public int dwProcessId;
            public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        private struct RM_PROCESS_INFO
        {
            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;
        }

        private enum RM_APP_TYPE
        {
            RmUnknownApp = 0,
            RmMainWindow = 1,
            RmOtherWindow = 2,
            RmService = 3,
            RmExplorer = 4,
            RmConsole = 5,
            RmCritical = 1000
        }

        [SuppressUnmanagedCodeSecurity]
        private static class NativeMethods
        {
            /// <summary>
            /// Starts a new Restart Manager session.
            /// </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 must be 0.</param>
            /// <param name="strSessionKey">A null-terminated string that contains the session key to the new session. A GUID will work nicely.</param>
            /// <returns>Error code. 0 is successful.</returns>
            [DllImport("RSTRTMGR.DLL", CharSet = CharSet.Unicode, PreserveSig = true, SetLastError = true, ExactSpelling = true)]
            public static extern int RmStartSession(out uint pSessionHandle, int dwSessionFlags, string strSessionKey);

            /// <summary>
            /// Ends the Restart Manager session.
            /// </summary>
            /// <param name="pSessionHandle">A handle to an existing Restart Manager session.</param>
            /// <returns>Error code. 0 is successful.</returns>
            [DllImport("RSTRTMGR.DLL")]
            public static extern int RmEndSession(uint pSessionHandle);

            /// <summary>
            /// Registers resources to a Restart Manager session. 
            /// </summary>
            /// <param name="pSessionHandle">A handle to an existing Restart Manager session.</param>
            /// <param name="nFiles">The number of files being registered.</param>
            /// <param name="rgsFilenames">An array of strings of full filename paths.</param>
            /// <param name="nApplications">The number of processes being registered.</param>
            /// <param name="rgApplications">An array of RM_UNIQUE_PROCESS structures. </param>
            /// <param name="nServices">The number of services to be registered.</param>
            /// <param name="rgsServiceNames">An array of null-terminated strings of service short names.</param>
            /// <returns>Error code. 0 is successful.</returns>
            [DllImport("RSTRTMGR.DLL", CharSet = CharSet.Unicode)]
            public static extern int RmRegisterResources(uint pSessionHandle, uint nFiles, string[] rgsFilenames, uint nApplications, [In] RM_UNIQUE_PROCESS[] rgApplications, uint nServices, string[] rgsServiceNames);

            /// <summary>
            /// Gets a list of all applications and services that are currently using resources that have been registered with the Restart Manager session.
            /// </summary>
            /// <param name="dwSessionHandle">A handle to an existing Restart Manager session.</param>
            /// <param name="pnProcInfoNeeded">A pointer to an array size necessary to receive RM_PROCESS_INFO structures</param>
            /// <param name="pnProcInfo">A pointer to the total number of RM_PROCESS_INFO structures in an array and number of structures filled.</param>
            /// <param name="rgAffectedApps">An array of RM_PROCESS_INFO structures that list the applications and services using resources that have been registered with the session.</param>
            /// <param name="lpdwRebootReasons">Pointer to location that receives a value of the RM_REBOOT_REASON enumeration that describes the reason a system restart is needed.</param>
            /// <returns>Error code. 0 is successful.</returns>
            [DllImport("RSTRTMGR.DLL")]
            public static extern int RmGetList(uint dwSessionHandle, out uint pnProcInfoNeeded, ref uint pnProcInfo, [In, Out] RM_PROCESS_INFO[] rgAffectedApps, ref uint lpdwRebootReasons);
        }


        static void Main(string[] args)
        {
            Console.WriteLine("Starting...");
            string[] file1 = new string[1];
            MessageBox.Show("Debug C#");
            file1[0] = @"C:\ProcessMonitor.zip";
            //DirectoryInfo dirInfo = new DirectoryInfo(folder);

            GetProcessesUsingFiles(file1);
            Console.WriteLine("End");``
        }
    }
}

【问题讨论】:

    标签: winapi restartmanager


    【解决方案1】:

    Easy File Locker 只是在这个词的非正式意义上“锁定”文件,即,它保护文件不被访问,但它不是通过获得文件锁定来做到这一点。相反,它使用了一种低级技术(文件系统过滤器驱动程序),该技术与防病毒软件保护其文件免受任何未经授权的访问的方式大体相似。 Restart Manager API 不打算也不会处理这种情况。

    您的应用程序几乎肯定也不需要处理这种情况。这意味着 Easy File Locker 不是满足您特定需求的合适工具;扔掉。


    为什么 RmGetList() 使用一个文件锁定实用程序会失败,但可以使用 另一个?

    要回答这个问题,我们需要了解RmGetList 在内部是如何工作的。在您的情况下,我们为它提供了一个文件名,它的输出是一个 RM_PROCESS_INFO 结构的数组。为了构建这个数组,Windows 必须确定哪些进程正在使用该文件。但是 Windows 是如何做到这一点的呢?

    函数ZwQueryInformationFile(由ntdll.dll导出)可以返回很多关于文件的信息。 FILE_INFORMATION_CLASS 枚举中的选项之一是

    FileProcessIdsUsingFileInformation

    FILE_PROCESS_IDS_USING_FILE_INFORMATION 结构。这个值是 保留给系统使用。此值从 Windows 开始可用 远景。

    wdm.h(这是一个来自 windows WDK 的众所周知的文件)中,我们发现

    typedef  struct _FILE_PROCESS_IDS_USING_FILE_INFORMATION {
        ULONG NumberOfProcessIdsInList;
        ULONG_PTR ProcessIdList[1];
    } FILE_PROCESS_IDS_USING_FILE_INFORMATION, *PFILE_PROCESS_IDS_USING_FILE_INFORMATION;
    

    所以这个选项正是我们需要的!

    算法是这样的:

    1. 使用FILE_READ_ATTRIBUTES 访问权限打开文件(对于这个信息类来说已经足够了)
    2. 致电ZwQueryInformationFile(..,FileProcessIdsUsingFileInformation); 如果NumberOfProcessIdsInList != 0 走ProcessIdList
    3. ProcessId打开进程,用ProcessStartTime查询它(见 GetProcessTimes) 等属性填写 RM_PROCESS_INFO

    既然我们知道了它是如何工作的,那么让我们看看您正在使用的两个实用程序。

    第二个是一个非常简单的应用程序,它提供了源代码。它所做的只是用dwShareMode = 0 调用CreateFile。这将获得文件的排他锁,确保任何使用dwDesiredAccess 打开文件的尝试 包含FILE_READ_DATAFILE_WRITE_DATADELETE 将失败并显示ERROR_SHARING_VIOLATION。但是,它不会阻止我们使用dwDesiredAccess = FILE_READ_ATTRIBUTES 打开文件,因此对 RmGetList() 的调用仍然可以正常工作。

    但第一个工具,来自 XOSLAB 的 Easy File Locker,正在使用微过滤器驱动程序 (HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\xlkfs) 来限制对文件的访问。此驱动程序返回STATUS_ACCESS_DENIED(转换为Win32 ERROR_ACCESS_DENIED)以供任何 尝试打开文件。因此,当RmGetList 尝试在步骤 (1) 中打开文件时,您会收到错误 ERROR_ACCESS_DENIED,并且(因为 API 不知道如何处理)此错误代码会返回给您。

    仅此而已。该工具根本没有达到您的预期。

    【讨论】:

    • 出色的分析!我已经重写以更正语法并提高可读性,并且还在顶部添加了一个技术含量较低的摘要,以使对 Windows 内部结构不太熟悉的读者受益。我希望这能得到您的认可。如果您同意摘要是准确的,并且我在重写时没有犯任何错误,我们可以删除免责声明。或者当然您可能希望还原整个编辑;不会冒犯。 :-)
    • @HarryJohnston - 非常感谢。你们真的都理解正确和准确的编辑答案 - 让它更具可读性。不幸的是英语是我的弱点
    猜你喜欢
    • 2021-03-29
    • 2015-09-06
    • 2016-12-11
    • 2023-03-08
    • 2021-03-19
    • 1970-01-01
    • 2011-03-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多