2016-12-27 65 views
2

我遇到Restart Manager API的一個奇怪問題:RmGetlist()。 爲了模擬一個文件鎖定的情況下,我想提出使用下列第三方文件鎖定工具:當使用Ez文件鎖定器鎖定文件但使用另一個文件鎖定實用程序時,RmGetList()API失敗

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

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

奇怪這裏的問題是, ,這兩個實用程序都會鎖定某個文件,但是,RMGetList()會因第一個文件鎖定實用程序(Ez文件鎖定)導致Access拒絕錯誤(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");`` 
     } 
    } 
} 

回答

3

簡易文件櫃被「鎖定」只有兩個字,即非正式意義上的文件,它可以保護訪問的文件,但它確實不要通過獲取文件上的鎖來實現。相反,它使用較低級別的技術(文件系統過濾器驅動程序),該技術與防病毒軟件保護其文件免受未經授權的訪問的方式大致相同。重新啓動管理器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 Vista開始。

wdm.h(這是從窗戶一個衆所周知的文件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

因此,現在我們知道如何工作,讓我們看看您使用的兩個實用程序。

第二個是一個非常簡單的應用程序,它提供的源代碼。它所要做的就是撥打CreateFiledwShareMode = 0。這會獲得對該文件的獨佔鎖定,以確保任何嘗試打開包含FILE_READ_DATAFILE_WRITE_DATADELETEdwDesiredAccess 的文件都將失敗,並顯示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)中嘗試打開文件時(以及API不知道如何處理此操作),將返回錯誤代碼ERROR_ACCESS_DENIED,並返回給您。

就是這樣。這個工具根本就沒有做你期望的事情。

+0

優秀分析!爲了糾正語法並提高可讀性,我重寫了這些內容,並在頂部添加了一個技術性較低的摘要,以便對Windows內部不太熟悉的讀者獲益。我希望這會得到您的批准。如果您同意該摘要是準確的,並且在重寫時沒有犯任何錯誤,我們可以刪除該免責聲明。或者當然你可能希望恢復整個編輯;將不會採取任何冒犯行爲。 :-) –

+0

@哈里約翰斯頓 - 非常感謝你。你真的都明白正確和準確的編輯答案 - 使其更具可讀性。不幸的是,英語是我的弱點 – RbMm