windivert-sys 0.8.0

Raw FFI bindings for WinDivert user mode library
Documentation
use std::{
    env, fs,
    process::{Command, Stdio},
};

use cc::{Build, Tool};

fn print_env(compiler: &Tool) {
    eprintln!("Environment variables:");
    for (k, v) in env::vars() {
        eprintln!("{}={}", k, v);
    }
    eprintln!("\nCompiler:\n{}", compiler.path().to_string_lossy());

    eprintln!("\nCompiler environment variables:");
    for (k, v) in compiler.env().iter() {
        eprintln!("{}={}", k.to_string_lossy(), v.to_string_lossy());
    }
    eprintln!("");
}

fn main() {
    if std::env::var("DOCS_RS").is_err() {
        let out_dir = env::var("OUT_DIR").unwrap();
        println!("cargo:rerun-if-changed=wrapper.h");
        println!("cargo:rerun-if-env-changed=WINDIVERT_PATH");
        println!("cargo:rerun-if-env-changed=WINDIVERT_DLL_OUTPUT");
        println!("cargo:rerun-if-env-changed={}/WinDivert.dll", &out_dir);
        println!("cargo:rerun-if-env-changed={}/WinDivert.lib", &out_dir);

        println!("cargo:rustc-link-lib=dylib=WinDivert");
        println!("cargo:rustc-link-search=native={}", &out_dir);

        if let Ok(lib_path) = env::var("WINDIVERT_PATH") {
            println!("cargo:warning=Copying windivert dll, lib & sys files from the path provided if present.");
            for f in fs::read_dir(&lib_path).unwrap() {
                let file = f.unwrap();
                if let Some(name) = file.file_name().to_str() {
                    match name {
                        "WinDivert.dll" | "WinDivert.lib" | "WinDivert32.sys"
                        | "WinDivert64.sys" => {
                            let _ = fs::copy(file.path(), format!("{}/{}", &out_dir, &name));
                        }
                        _ => {}
                    }
                }
            }
        } else {
            println!("cargo:rustc-link-search=native={}", &out_dir);
            println!("cargo:warning=Environment variable WINDIVERT_PATH not found, building WinDivert from source.");
            build_windivert();
        };

        let arch = match env::var("CARGO_CFG_TARGET_ARCH").unwrap().as_ref() {
            "x86" => "32",
            "x86_64" => "64",
            _ => panic!("Unsupported target architecture!"),
        };

        if let Err(_) = fs::metadata(format!("{}\\WinDivert{}.sys", &out_dir, &arch)) {
            println!(
                "cargo:warning=WinDivert{}.sys not found on the same directory as the dll.",
                arch
            )
        }
    }
}

fn build_windivert() {
    let build = Build::new();
    let compiler = build.get_compiler();
    print_env(&compiler);

    if compiler.is_like_msvc() {
        msvc_compile(build);
    } else if compiler.is_like_gnu() {
        if !env::var("TARGET").unwrap().contains("windows") {
            panic!("This library only works for windows targets")
        }
        gnu_compile(build);
    }
}

const MSVC_ARGS: &str = r#"/Ivendor\include /ZI /JMC /nologo /W1 /WX- /diagnostics:column /O1 /Oi /D WIN32 /D NDEBUG /D _WINDOWS /D _USRDLL /D DLL_EXPORTS /D _WINDLL /Gm- /EHsc /MDd /GS- /fp:precise /Zc:wchar_t /Zc:forScope /Zc:inline /Gd /TC /FC /errorReport:queue vendor\dll\windivert.c /link /ERRORREPORT:QUEUE /INCREMENTAL /NOLOGO kernel32.lib advapi32.lib /NODEFAULTLIB /DEF:vendor/dll/windivert.def /MANIFEST /manifest:embed /DEBUG:FASTLINK /TLDLIB:1 /ENTRY:"WinDivertDllEntry" /DYNAMICBASE /NXCOMPAT /DLL"#;
fn msvc_compile(build: Build) {
    let compiler = build.get_compiler();

    let out_dir = env::var("OUT_DIR").unwrap();
    println!("cargo:rustc-link-search={}", &out_dir);

    let mut cmd = compiler.to_command();

    cmd.arg(format!(r#"/Fo{}\WinDivert.obj"#, &out_dir));
    cmd.arg(format!(r#"/Fd{}\WinDivert.pdb"#, &out_dir));

    cmd.args(MSVC_ARGS.split(" "));

    let arch = match env::var("CARGO_CFG_TARGET_ARCH").unwrap().as_ref() {
        "x86" => "x86",
        "x86_64" => "x64",
        _ => panic!("Unsupported target architecture!"),
    };
    cmd.arg(format!("/MACHINE:{}", arch));

    cmd.arg(format!(r#"/PDB:{}\WinDivertDll.pdb"#, &out_dir));
    cmd.arg(format!(r#"/OUT:{}\WinDivert.dll"#, &out_dir));
    cmd.arg(format!(r#"/IMPLIB:{}\WinDivert.lib"#, &out_dir));

    eprintln!("\nCompiling windivert\n");
    if let Ok(out) = cmd.output() {
        if !out.status.success() {
            eprint!(
                "\nERROR: {:?}\n{}\n",
                &out.status,
                String::from_utf8_lossy(&out.stdout),
            );
            panic!()
        }
    } else {
        panic!("Error compiling windivert dll.");
    }

    if let Ok(dylib_save_dir) = env::var("WINDIVERT_DLL_OUTPUT") {
        let _ = fs::copy(
            format!(r#"{}\WinDivert.dll"#, &out_dir),
            format!(r#"{}\WinDivert.dll"#, &dylib_save_dir),
        );
        let _ = fs::copy(
            format!(r#"{}\WinDivert.lib"#, &out_dir),
            format!(r#"{}\WinDivert.lib"#, &dylib_save_dir),
        );
    } else {
        println!("cargo:warning=Environment variable WINDIVERT_DLL_OUTPUT not found, compiled dll & lib files will be stored on {}", &out_dir);
    };
}

fn gnu_compile(build: Build) {
    let compiler = build.get_compiler();

    let mut cmd = compiler.to_command();
    cmd.stdout(Stdio::inherit()).stderr(Stdio::inherit());

    let out_dir = env::var("OUT_DIR").unwrap();
    println!("cargo:rustc-link-search={}", &out_dir);

    let mangle = if env::var("CARGO_CFG_TARGET_ARCH").unwrap() == "x86" {
        "_"
    } else {
        ""
    };
    set_gnu_c_options(&mut cmd);
    cmd.arg(format!("-Wl,--entry=${}WinDivertDllEntry", &mangle));
    cmd.args(&["-c", "vendor/dll/windivert.c"]);
    cmd.args(&["-o", &format!("{}/WinDivert.o", &out_dir)]);
    cmd.output().expect("Error compiling windivert c library");

    let mut cmd = build.get_compiler().to_command();
    set_gnu_c_options(&mut cmd);
    cmd.args(&["-o", &format!("{}/WinDivert.dll", &out_dir)]);
    cmd.args(&[
        &format!("{}/WinDivert.o", &out_dir),
        "vendor/dll/windivert.def",
        "-nostdlib",
    ]);
    set_gnu_c_libs(&mut cmd);
    cmd.output().expect("Error building windivert dll");

    let strip = Build::new()
        .get_compiler()
        .path()
        .to_string_lossy()
        .replace("gcc", "strip");
    let mut strip = Command::new(strip);
    strip.stdout(Stdio::inherit()).stderr(Stdio::inherit());

    strip.arg(&format!("{}/WinDivert.dll", out_dir));
    let _ = strip.output().expect("Error striping windivert dll");

    let dlltool = Build::new()
        .get_compiler()
        .path()
        .to_string_lossy()
        .replace("gcc", "dlltool");
    let mut dlltool = Command::new(dlltool);
    dlltool.stdout(Stdio::inherit()).stderr(Stdio::inherit());

    dlltool.args(&["--dllname", &format!("{}/WinDivert.dll", &out_dir)]);
    dlltool.args(&["--def", "vendor/dll/windivert.def"]);
    dlltool.args(&["--output-lib", &format!("{}/WinDivert.lib", &out_dir)]);
    let _ = dlltool.output().expect("Error building windivert lib");

    let _ = fs::remove_file(format!("{}/WinDivert.o", &out_dir));
}

fn set_gnu_c_options(cmd: &mut Command) {
    cmd.args(&[
        "-fno-ident",
        "-shared",
        "-Wall",
        "-Wno-pointer-to-int-cast",
        "-Os",
        "-Ivendor/include/",
        "-Wl,--enable-stdcall-fixup",
    ]);
}

fn set_gnu_c_libs(cmd: &mut Command) {
    cmd.args(&["-lkernel32", "-ladvapi32"]);
}