wininskit 0.1.2

Thin checked wrappers over the Win32 an installer needs: elevation, ACLs, services, the Restart Manager, the registry and shortcuts.
use std::{mem, path::Path, ptr};

use windows_sys::Win32::{
    Foundation::{ERROR_MORE_DATA, ERROR_SUCCESS},
    System::RestartManager::{
        CCH_RM_MAX_APP_NAME, CCH_RM_SESSION_KEY, RM_PROCESS_INFO, RmEndSession, RmGetList,
        RmRegisterResources, RmStartSession,
    },
};

use crate::{
    error::{Error, Result},
    process::Process,
    wide::{from_wide, wide_path},
};

/// Which processes are holding these files open.
///
/// The Restart Manager names the processes actually locking the files about to
/// be replaced, including ones nobody would have thought to check for. That is
/// the difference between "something is in the way" and "Explorer has a preview
/// handler open on this DLL".
///
/// An empty list means nothing holds them.
pub fn locking_processes(files: &[&Path]) -> Result<Vec<Process>> {
    if files.is_empty() {
        return Ok(Vec::new());
    }

    unsafe {
        let mut session = 0u32;
        let mut key = [0u16; CCH_RM_SESSION_KEY as usize + 1];
        let status = RmStartSession(&mut session, 0, key.as_mut_ptr());
        if status != ERROR_SUCCESS {
            return Err(Error::code("RmStartSession", status));
        }
        let session = SessionGuard(session);

        // The API keeps its own copies, so these only have to outlive the call.
        let wide: Vec<Vec<u16>> = files.iter().map(|p| wide_path(p)).collect();
        let pointers: Vec<*const u16> = wide.iter().map(|w| w.as_ptr()).collect();

        let status = RmRegisterResources(
            session.0,
            pointers.len() as u32,
            pointers.as_ptr(),
            0,
            ptr::null(),
            0,
            ptr::null(),
        );
        if status != ERROR_SUCCESS {
            return Err(Error::code("RmRegisterResources", status));
        }

        // The count can grow between asking and reading, so this asks again
        // rather than trusting the first answer.
        let mut capacity = 16u32;
        loop {
            let mut needed = 0u32;
            let mut count = capacity;
            let mut reason = 0u32;
            let mut info: Vec<RM_PROCESS_INFO> = vec![mem::zeroed(); capacity as usize];

            let status = RmGetList(
                session.0,
                &mut needed,
                &mut count,
                info.as_mut_ptr(),
                &mut reason,
            );

            if status == ERROR_MORE_DATA {
                capacity = needed.max(capacity * 2);
                continue;
            }
            if status != ERROR_SUCCESS {
                return Err(Error::code("RmGetList", status));
            }

            info.truncate(count as usize);
            return Ok(info
                .iter()
                .map(|entry| Process {
                    id: entry.Process.dwProcessId,
                    name: app_name(entry),
                    image: None,
                })
                .collect());
        }
    }
}

fn app_name(entry: &RM_PROCESS_INFO) -> String {
    let name = from_wide(&entry.strAppName[..CCH_RM_MAX_APP_NAME as usize]);
    if name.is_empty() {
        format!("process {}", entry.Process.dwProcessId)
    } else {
        name
    }
}

struct SessionGuard(u32);

impl Drop for SessionGuard {
    fn drop(&mut self) {
        unsafe { RmEndSession(self.0) };
    }
}