RmGetList() API не работает, когда файл заблокирован с помощью Ez File Locker, но работает с другой утилитой блокировки файлов

У меня возникла странная проблема с API-интерфейсом Restart Manager: RmGetlist(). Чтобы смоделировать сценарий блокировки файлов, я использую следующие сторонние утилиты блокировки файлов:

EZ File Lock - http://www.xoslab.com/efl.html -

Файловый шкафчик http://www.jensscheffler.de/filelocker

Странная проблема заключается в том, что обе эти утилиты блокируют определенный файл, однако RMGetList() завершается с ошибкой "Отказано в доступе" (5) с первой утилитой блокировки файлов (Ez File lock), тогда как она работает со второй утилитой блокировки файлов.

Может ли кто-нибудь дать мне знать, что здесь может быть не так?Почему 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");``
        }
    }
}

1 ответ

Easy File Locker "блокирует" файл только в неформальном смысле этого слова, то есть защищает файлы от доступа, но не делает этого путем получения блокировки на файл. Вместо этого он использует технологию более низкого уровня (драйвер фильтра файловой системы), которая в целом похожа на то, как антивирусное программное обеспечение защищает свои файлы от любого несанкционированного доступа. API Restart Manager не предназначен и не имеет отношения к такого рода сценариям.

Вашему приложению почти наверняка не нужно иметь дело с такого рода сценарием. Это означает, что Easy File Locker не подходит для ваших конкретных нужд; выброси это.


Почему RmGetList() не работает с одной утилитой блокировки файлов, но работает с другой?

Чтобы ответить на это, нам нужно понять, как RmGetList работает внутри. В вашем случае мы предоставляем ему имя файла, а его вывод представляет собой массив RM_PROCESS_INFO структур. Чтобы построить этот массив, Windows должна определить, какие процессы используют файл. Но как Windows это делает?

Функция ZwQueryInformationFile (экспортируется ntdll.dll) может вернуть много информации о файле. Один из вариантов в FILE_INFORMATION_CLASS перечисление

FileProcessIdsUsingFileInformation

Структура FILE_PROCESS_IDS_USING_FILE_INFORMATION. Это значение зарезервировано для использования системой. Это значение доступно начиная с Windows Vista.

И в 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

Итак, теперь, когда мы знаем, как это работает, давайте посмотрим на две утилиты, которые вы используете.

Второе - очень простое приложение, которое предоставляет исходный код. Все, что он делает, это звонит CreateFile с dwShareMode = 0, Это приобретает эксклюзивную блокировку файла, гарантируя, что любая попытка открыть файл с dwDesiredAccess который содержит FILE_READ_DATA или же FILE_WRITE_DATA или же DELETE потерпит неудачу с ERROR_SHARING_VIOLATION, Это, однако, не мешает нам открыть файл с dwDesiredAccess = FILE_READ_ATTRIBUTES так что вызов RmGetList() все равно будет работать правильно.

Но первый инструмент, Easy File Locker от XOSLAB, использует драйвер минифильтра (HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\xlkfs) ограничить доступ к файлу. Этот драйвер возвращается STATUS_ACCESS_DENIED (который конвертируется в Win32 ERROR_ACCESS_DENIED) за любую попытку открыть файл. Из-за этого вы получаете ошибку ERROR_ACCESS_DENIED когда RmGetList пытается открыть файл на шаге (1) и (поскольку API не знает, что с этим делать), этот код ошибки возвращается вам.

Это все, что нужно сделать. Инструмент просто не делает то, что вы ожидали.

Другие вопросы по тегам