使用Restart Manager从Rust解锁文件

w6lpcovy  于 2023-10-20  发布在  其他
关注(0)|答案(3)|浏览(127)

我尝试使用windows crate将此函数从C++转换为Rust

#include <iostream>
#include "windows.h"

#include <RestartManager.h>
#pragma comment(lib ,"Rstrtmgr.lib")

BOOL ReleaseFileLock(LPCTSTR pFilePath)
{
    BOOL bResult = FALSE;

    DWORD dwSession;
    WCHAR szSessionKey[CCH_RM_SESSION_KEY + 1] = { 0 };
    DWORD dwError = RmStartSession(&dwSession, 0, szSessionKey);
    if (dwError == ERROR_SUCCESS)
    {
        dwError = RmRegisterResources(dwSession, 1, &pFilePath,
            0, NULL, 0, NULL);
        if (dwError == ERROR_SUCCESS)
        {
            UINT nProcInfoNeeded = 0;
            UINT nProcInfo = 0;
            RM_PROCESS_INFO rgpi[1];
            DWORD dwReason;

            dwError = RmGetList(dwSession, &nProcInfoNeeded,
                &nProcInfo, rgpi, &dwReason);
            if (dwError == ERROR_SUCCESS ||
                dwError == ERROR_MORE_DATA)
            {
                if (nProcInfoNeeded > 0)
                {
                    //If current process does not have enough privileges to close one of
                    //the "offending" processes, you'll get ERROR_FAIL_NOACTION_REBOOT
                    dwError = RmShutdown(dwSession, RmForceShutdown, NULL);
                    if (dwError == ERROR_SUCCESS)
                    {
                        bResult = TRUE;
                    }
                }
                else
                    bResult = TRUE;
            }
        }
    }

    RmEndSession(dwSession);

    SetLastError(dwError);
    return bResult;
}

我成功地导入了必要的函数,但作为一个起点,我试图简单地启动会话并关闭它,我得到了错误STATUS_ACCESS_VIOLATION

use windows_sys::core::PWSTR;
use windows::Win32::System::RestartManager::{
    RmStartSession,
    RmEndSession,
    RmRegisterResources,
    RmGetList,
    RmShutdown,
    RmForceShutdown,
};

fn release_file_lock(path: &str) -> Result<(), Box<dyn std::error::Error>> {    

    let psessionhandle = std::ptr::null_mut();
    let dwsessionflags: u32 = 0;
    let strsessionkey = std::ptr::null_mut();
    unsafe {
        let result = RmStartSession(psessionhandle, dwsessionflags, windows::core::PWSTR(strsessionkey));
        println!("{:?} {:?}", result, psessionhandle);
        RmEndSession(*psessionhandle); // < --- STATUS_ACCESS_VIOLATION
    };
    Ok(())
}

我想知道转换代码的流程应该是什么。
目前,我正在docs.rs/windows中查找并遵循每个函数,我试图拟合参数,但看起来我在这个过程中错过了一些重要的东西。

jvlzgdj9

jvlzgdj91#

在引发访问冲突之前,代码打印出:

160 0x0

第一个数字160是RmStartSession()调用的返回值。这是一个system error code,由命名常量ERROR_BAD_ARGUMENTS标识,该常量转换为错误文本 “One or more arguments are not correct "
因此,防止访问冲突的第一步是观察RmStartSession()和bail的返回值,如果它不是ERROR_SUCCESS,就像这样:

let result = RmStartSession(
    psessionhandle,
    dwsessionflags,
    windows::core::PWSTR(strsessionkey),
);
if result != ERROR_SUCCESS.0 {
    return Err("Failed to start session".into());
}

解决了这个问题,我们就可以继续前进,找出哪些论点是错误的。必要的信息可以从Microsoft的文档中获得,该文档详细说明了函数调用合同。RmStartSession的相关参数为:
pSessionHandle
指向重新启动管理器会话句柄的指针。
strSessionKey
一个以null结尾的字符串,包含新会话的会话密钥。调用RmStartSession函数前必须先分配字符串。
任何一个指针都必须是 * 有效 * 的,并且引用调用者提供的内存。前者是指向DWORD(Rust中为u32)的指针,而后者是指向长度为CCH_RM_SESSION_KEY + 1WCHAR s(Rust中为u16 s)数组的开始的指针。应用这些更改会导致以下代码:

fn release_file_lock(path: &str) -> Result<(), Box<dyn std::error::Error>> {
    let mut session_handle = 0_u32;
    let mut session_key = [0_u16; CCH_RM_SESSION_KEY as usize + 1];
    unsafe {
        let result = RmStartSession(&mut session_handle, 0, PWSTR(session_key.as_mut_ptr()));
        if result != ERROR_SUCCESS.0 {
            return Err("Failed to start session".into());
        }
        println!("{:?} {:?}", result, session_handle);
        RmEndSession(session_handle);
    };
    Ok(())
}
djp7away

djp7away2#

我认为没有正确的方法将代码从一种语言转换为另一种语言。如果您不熟悉该语言,可以像在提供的rust片段中所做的那样逐层进行。通过这种方式,您可以在转换过程中更轻松地学习目标语言的概念。
在您的情况下,我建议每次只关注一个API调用,并检查结果是否与C源代码的结果匹配。让我分享一下我对转换主题的一些看法:
1.在第一次迭代中保持函数的一致性。例如,您的C
代码具有函数签名BOOL ReleaseFileLock(LPCTSTR pFilePath),但Rust具有release_file_lock(path: &str) -> Result<...>。在unicode模式下,LPCTSTR是指在windows中表示UTF 16编码的宽字符。Rust将字符串存储在UTF8中,但您稍后调用的API将使用UTF 16。一开始保持签名相同,可以确保您对函数行为的期望也是相同的。
1.不要关心转换期间错误处理的细节。您已经从Result<(), Box<dyn std::error::Error>>开始,这意味着某种基本的错误转发。我通常会保持简单,暂时跳过错误处理部分。如果遇到返回Result<...>的API函数,只需执行unwrap。当然,由于unwrap,这可能有句柄泄漏的副作用,但它只是在不熟悉的领域中更快地进行原型设计和转换代码。稍后在转换的终结过程中引入错误处理,逐步通过终止和资源管理来确保正确性。
1.对类型、类型转换和内存概念要格外小心!你应该了解C和Rust的区别,并知道API期望从你传递的参数中得到什么。理解C代码中变量的意图和内容,然后研究如何使用Rust实现相同的目标。
让我们来看看转换过程,并探讨您在转换过程中犯的一些错误。下面的代码片段显示了基线的C++示例的精简版本。我做了一些小的类型修改,将RmEndSession移到了successful分支中。

#include <Windows.h>
#include <RestartManager.h>
#pragma comment(lib ,"Rstrtmgr.lib")

bool ReleaseFileLock(LPCWSTR pFilePath)
{
    DWORD dwSession = 0;
    WCHAR szSessionKey[CCH_RM_SESSION_KEY + 1] = { 0 };
    DWORD dwError = RmStartSession(&dwSession, 0, szSessionKey);
    if (dwError == ERROR_SUCCESS)
    {
        RmEndSession(dwSession);
        return true;
    }
    return false;
}

int main() {
    ReleaseFileLock(L"C:\\somepath");
}

下面是向Rust的转换:

use windows::{Win32::{System::RestartManager::{
    RmStartSession,
    RmEndSession, CCH_RM_SESSION_KEY,
}, Foundation::{ERROR_SUCCESS, WIN32_ERROR}}, core::{PWSTR, PCWSTR, w}};

unsafe fn release_file_lock(file_path: PCWSTR) -> bool {
    let mut session: u32 = 0;
    let mut session_key_buffer = [0_u16; (CCH_RM_SESSION_KEY as usize) + 1];
    let session_key = PWSTR(session_key_buffer.as_mut_ptr());
    let result = RmStartSession(&mut session, 0, session_key);
    if WIN32_ERROR(result) == ERROR_SUCCESS {
        RmEndSession(session);
        return true;
    }
    return false;
}

fn main() {
    unsafe {
        release_file_lock(w!("C:\\somepath"));
    }
}

正如您所看到的,Rust解决方案的第一次迭代几乎与C解决方案相同。在Rust中有DWORDu32WCHARu16的类型差异。需要额外的类型转换,如WIN32_ERROR(result)PWSTR(session_key_buffer.as_mut_ptr()),将宽字符片转换为API调用所需的类型。但基本思想和变量内存布局是等价的。
在你尝试转换的过程中,很多C
代码的概念都被无意中遗漏了,或者被修改了,改变了变量的意图。在Rust版本中,您可以:
1.在对句柄执行某些操作之前,未检查API调用是否成功。为了避免在调试过程中产生垃圾数据,您应该始终检查返回值是否成功。
1.使用指针let psessionhandle = std::ptr::null_mut(),其中最初声明了值DWORD dwSession。值和指针的混淆还导致了由于空指针的解引用而导致的访问冲突。在很多情况下,传递给C中的函数的可变引用(如&dwSession)在Rust中转换为&mut dwSession,或者对于复杂类型转换为.as_mut_ptr(),所以你只需要记住C代码的原始想法。
1.没有分配WCHAR szSessionKey[]作为API的缓冲区,而是传入了std::ptr::null_mut()。API只需要一个指向内存的指针,它可以在其中存储这个会话密钥。在C中,这个指针来自分配给WCHAR szSessionKey[]的堆栈,在函数调用过程中,你不必像&szSessionKey那样引用它,因为在C中,指向堆栈分配内存块的变量已经是它的指针。在Rust中,你必须声明堆栈内存,然后专门将其指针与.as_mut_ptr()传递给PWSTR(*mut u16),以实现等效的概念。
有了这个建议,你应该能够转换整个代码。请记住,在第一次迭代之后,您可以返回并引入适当的错误处理,如果需要,您还可以将函数参数更新为更方便的类型,例如字符串切片而不是Windows特定类型。

3b6akqbq

3b6akqbq3#

感谢您的帮助,我能够深入研究,并成功地创建了整个功能,它工作。

use windows::{Win32::{System::RestartManager::{
    RmStartSession,
    RmRegisterResources,
    RmEndSession, 
    RmGetList,
    RmShutdown,
    RmForceShutdown,
    CCH_RM_SESSION_KEY,
    RM_PROCESS_INFO
}, Foundation::{ERROR_SUCCESS, WIN32_ERROR, ERROR_MORE_DATA}}, core::{PWSTR, PCWSTR, w}};

unsafe fn release_file_lock(file_path: PCWSTR) -> bool {
    let mut session: u32 = 0;
    let mut session_key_buffer = [0_u16; (CCH_RM_SESSION_KEY as usize) + 1];
    let session_key = PWSTR(session_key_buffer.as_mut_ptr());
    let result = RmStartSession(&mut session, 0, session_key);
    if WIN32_ERROR(result) == ERROR_SUCCESS {
        let result = RmRegisterResources(
            session,
             Some(&[file_path]), 
             None, 
             None
        );
        if WIN32_ERROR(result) == ERROR_SUCCESS {
            let mut pnprocinfoneeded: u32 = 0;
            let mut rgaffectedapps: [RM_PROCESS_INFO; 1] = [RM_PROCESS_INFO{..Default::default()}];
            let mut lpdwrebootreasons: u32 = 0;
            let mut pnprocinfo: u32 = 0;
            let result = RmGetList(session, &mut pnprocinfoneeded, &mut pnprocinfo, Some(rgaffectedapps.as_mut_ptr()), &mut lpdwrebootreasons);
            if WIN32_ERROR(result) == ERROR_SUCCESS || WIN32_ERROR(result) == ERROR_MORE_DATA {
                if pnprocinfoneeded > 0 {
                    // If current process does not have enough privileges to close one of
                    // the "offending" processes, you'll get ERROR_FAIL_NOACTION_REBOOT
                    let result = RmShutdown(session, RmForceShutdown.0 as u32, None);
                    if WIN32_ERROR(result) == ERROR_SUCCESS {
                        // success
                        RmEndSession(session);
                        return true;
                    }
                } else {
                    // success
                    RmEndSession(session);
                    return true;
                }
            }
        }
        RmEndSession(session);
        return false;
    }
    return false;
}

相关问题