privilege 0.3.0

Cross-platform library for administrative permission handling.
Documentation
use std::ffi::OsStr;
use std::io;
use std::mem;
use std::os::raw::c_ushort;
use std::os::windows::ffi::OsStrExt;
use std::process::ExitStatus;
use std::ptr;

use windows_sys::Win32::System::Com::{
    CoInitializeEx, COINIT_APARTMENTTHREADED, COINIT_DISABLE_OLE1DDE,
};
use windows_sys::Win32::System::Threading::GetExitCodeProcess;
use windows_sys::Win32::System::Threading::WaitForSingleObject;
use windows_sys::Win32::System::Threading::INFINITE;
use windows_sys::Win32::UI::Shell::{ShellExecuteExW, SHELLEXECUTEINFOW};
use windows_sys::Win32::UI::Shell::SEE_MASK_NOASYNC;
use windows_sys::Win32::UI::Shell::SEE_MASK_NOCLOSEPROCESS;
use windows_sys::Win32::UI::WindowsAndMessaging::{SW_HIDE, SW_NORMAL};

use crate::runas::Command;

unsafe fn win_runas(cmd: *const c_ushort, args: *const c_ushort, show: bool) -> u32 {
    let mut code = 0;
    let mut sei: SHELLEXECUTEINFOW = mem::zeroed();
    let verb = "runas\0".encode_utf16().collect::<Vec<u16>>();
    CoInitializeEx(
        ptr::null(),
        COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE,
    );
    
    sei.cbSize = mem::size_of::<SHELLEXECUTEINFOW>() as _;
    sei.lpVerb = verb.as_ptr();
    sei.lpFile = cmd;
    sei.lpParameters = args;
    sei.fMask = SEE_MASK_NOASYNC | SEE_MASK_NOCLOSEPROCESS;
    sei.nShow = if show { SW_NORMAL } else { SW_HIDE } as _;

    if ShellExecuteExW(&mut sei) == 0 || sei.hProcess == 0 {
        return !0;
    }

    WaitForSingleObject(sei.hProcess, INFINITE);

    if GetExitCodeProcess(sei.hProcess, &mut code) == 0 {
        !0
    } else {
        code
    }
}

pub(crate) fn runas_root(cmd: &Command) -> io::Result<ExitStatus> {
    let mut params = String::new();
    for arg in cmd.args.iter() {
        let arg = arg.to_string_lossy();
        params.push(' ');
        if arg.len() == 0 {
            params.push_str("\"\"");
        } else if arg.find(&[' ', '\t', '"'][..]).is_none() {
            params.push_str(&arg);
        } else {
            params.push('"');
            for c in arg.chars() {
                match c {
                    '\\' => params.push_str("\\\\"),
                    '"' => params.push_str("\\\""),
                    c => params.push(c),
                }
            }
            params.push('"');
        }
    }

    let file = OsStr::new(&cmd.command)
        .encode_wide()
        .chain(Some(0))
        .collect::<Vec<_>>();
    let params = OsStr::new(&params)
        .encode_wide()
        .chain(Some(0))
        .collect::<Vec<_>>();

    unsafe {
        Ok(mem::transmute(win_runas(
            file.as_ptr(),
            params.as_ptr(),
            !cmd.hide,
        )))
    }
}