mirror of
				https://github.com/Cisco-Talos/clamav.git
				synced 2025-10-31 16:10:54 +00:00 
			
		
		
		
	 39a9c3b956
			
		
	
	
		39a9c3b956
		
	
	
	
	
		
			
			* Work with data as &[u8] instead String/&str to avoid unnecessary UTF-8 validation and reuse read buffers. * Make error handling more concise * Address Clippy-raised issues
		
			
				
	
	
		
			284 lines
		
	
	
	
		
			9.5 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			284 lines
		
	
	
	
		
			9.5 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| use std::env;
 | |
| use std::path::{Path, PathBuf};
 | |
| 
 | |
| use bindgen::builder;
 | |
| 
 | |
| // Note to maintainers: this is currently a hybrid of examination of the
 | |
| // CMake environment, and leaning on Rust `cfg` elements. Ideally, it
 | |
| // should be possible to work in this space (e.g., execute tests from an
 | |
| // IDE) without having to rely on CMake elements to properly link the
 | |
| // unit tests). Hence the bizarre mix of CMake inspection and Cargo-based
 | |
| // elements.
 | |
| //
 | |
| // It's handy to know that all the `cfg` goodies are defined here:
 | |
| //
 | |
| // https://doc.rust-lang.org/reference/conditional-compilation.html
 | |
| 
 | |
| // A list of environment variables to query to determine additional libraries
 | |
| // that need to be linked to resolve dependencies.
 | |
| const LIB_ENV_LINK: &[&str] = &[
 | |
|     "LIBSSL",
 | |
|     "LIBCRYPTO",
 | |
|     "LIBZ",
 | |
|     "LIBBZ2",
 | |
|     "LIBPCRE2",
 | |
|     "LIBXML2",
 | |
|     "LIBCURL",
 | |
|     "LIBJSONC",
 | |
|     "LIBCLAMMSPACK",
 | |
|     "LIBCLAMUNRARIFACE",
 | |
|     "LIBCLAMUNRAR",
 | |
|     "LIBICONV",
 | |
| ];
 | |
| 
 | |
| // The same, but additional values to check on Windows platforms
 | |
| const LIB_ENV_LINK_WINDOWS: &[&str] = &["LIBPTHREADW32", "LIBWIN32COMPAT"];
 | |
| 
 | |
| // Additional [verbatim] libraries to link on Windows platforms
 | |
| const LIB_LINK_WINDOWS: &[&str] = &["wsock32", "ws2_32", "Shell32", "User32"];
 | |
| 
 | |
| // Windows library names that must have the leading `lib` trimmed (if encountered)
 | |
| const WINDOWS_TRIM_LOCAL_LIB: &[&str] = &["libclamav", "libclammspack"];
 | |
| 
 | |
| // Generate bindings for these functions:
 | |
| const BINDGEN_FUNCTIONS: &[&str] = &[
 | |
|     "cli_ctx",
 | |
|     "cli_warnmsg",
 | |
|     "cli_dbgmsg_no_inline",
 | |
|     "cli_infomsg_simple",
 | |
|     "cli_errmsg",
 | |
|     "cli_append_virus",
 | |
|     "cli_versig2",
 | |
|     "cli_getdsig",
 | |
|     "cli_get_debug_flag",
 | |
| ];
 | |
| 
 | |
| // Generate bindings for these types (structs, enums):
 | |
| const BINDGEN_TYPES: &[&str] = &["cli_matcher", "cli_ac_data", "cli_ac_result"];
 | |
| 
 | |
| // Find the required functions and types in these headers:
 | |
| const BINDGEN_HEADERS: &[&str] = &[
 | |
|     "../libclamav/matcher.h",
 | |
|     "../libclamav/matcher-ac.h",
 | |
|     "../libclamav/others.h",
 | |
|     "../libclamav/dsig.h",
 | |
| ];
 | |
| 
 | |
| // Find the required headers in these directories:
 | |
| const BINDGEN_INCLUDE_PATHS: &[&str] = &[
 | |
|     "-I../libclamav",
 | |
|     "-I../libclamunrar_iface",
 | |
|     "-I../libclammspack",
 | |
| ];
 | |
| 
 | |
| // Write the bindings to this file:
 | |
| const BINDGEN_OUTPUT_FILE: &str = "src/sys.rs";
 | |
| 
 | |
| const C_HEADER_OUTPUT: &str = "clamav_rust.h";
 | |
| 
 | |
| // Environment variable name prefixes worth including for diags
 | |
| const ENV_PATTERNS: &[&str] = &["CARGO_", "RUST", "LIB"];
 | |
| 
 | |
| fn main() -> Result<(), &'static str> {
 | |
|     // Dump the command line and interesting environment variables for diagnostic
 | |
|     // purposes. These will end up in a 'stderr' file under the target directory,
 | |
|     // in a ".../clamav_rust-<hex>" subdirectory
 | |
| 
 | |
|     eprintln!("build.rs command line: {:?}", std::env::args());
 | |
|     eprintln!("Environment:");
 | |
|     std::env::vars()
 | |
|         .filter(|(k, _)| ENV_PATTERNS.iter().any(|prefix| k.starts_with(prefix)))
 | |
|         .for_each(|(k, v)| eprintln!("  {}={:?}", k, v));
 | |
| 
 | |
|     detect_clamav_build()?;
 | |
| 
 | |
|     // We only want to generate bindings for `cargo build`, not `cargo test`.
 | |
|     // FindRust.cmake defines $CARGO_CMD so we can differentiate.
 | |
|     let cargo_cmd = env::var("CARGO_CMD").unwrap_or_else(|_| "".into());
 | |
|     if cargo_cmd == "build" {
 | |
|         // Always generate the C-headers when CMake kicks off a build.
 | |
|         execute_cbindgen()?;
 | |
| 
 | |
|         // Only generate the `.rs` bindings when maintainer-mode is enabled.
 | |
|         //
 | |
|         // Bindgen requires libclang, which may not readily available, so we
 | |
|         // will commit the bindings to version control and use maintainer-mode
 | |
|         // to update them, as needed.
 | |
|         // On the plus-side, this means that our `.rs` file is present before our
 | |
|         // first build, so at least rust-analyzer will be happy.
 | |
|         let maintainer_mode = env::var("MAINTAINER_MODE").unwrap_or_else(|_| "".into());
 | |
|         if maintainer_mode == "ON" {
 | |
|             execute_bindgen()?;
 | |
|         }
 | |
|     } else {
 | |
|         eprintln!("NOTE: Not generating bindings because CARGO_CMD != build");
 | |
|     }
 | |
| 
 | |
|     Ok(())
 | |
| }
 | |
| 
 | |
| /// Use bindgen to generate Rust bindings to call into C libraries.
 | |
| fn execute_bindgen() -> Result<(), &'static str> {
 | |
|     let build_dir = PathBuf::from(env::var("CARGO_TARGET_DIR").unwrap_or_else(|_| ".".into()));
 | |
|     let build_include_path = format!("-I{}", build_dir.join("..").to_str().unwrap());
 | |
| 
 | |
|     // Configure and generate bindings.
 | |
|     let mut builder = builder()
 | |
|         // Silence code-style warnings for generated bindings.
 | |
|         .raw_line("#![allow(non_snake_case, non_camel_case_types, non_upper_case_globals)]")
 | |
|         // Make the bindings pretty.
 | |
|         .rustfmt_bindings(true)
 | |
|         // Disable the layout tests.
 | |
|         // We're commiting to source control. Pointer width, integer size, etc
 | |
|         // are probably not the same when generated as when compiled.
 | |
|         .layout_tests(false)
 | |
|         // Enable bindgen to find generated headers in the build directory, too.
 | |
|         .clang_arg(build_include_path);
 | |
| 
 | |
|     for &include_path in BINDGEN_INCLUDE_PATHS {
 | |
|         builder = builder.clang_arg(include_path);
 | |
|     }
 | |
|     for &header in BINDGEN_HEADERS {
 | |
|         builder = builder.header(header);
 | |
|     }
 | |
|     for &c_function in BINDGEN_FUNCTIONS {
 | |
|         builder = builder.allowlist_function(c_function);
 | |
|     }
 | |
|     for &c_type in BINDGEN_TYPES {
 | |
|         builder = builder.allowlist_type(c_type);
 | |
|     }
 | |
| 
 | |
|     // Generate!
 | |
|     let bindings = builder.generate().unwrap();
 | |
| 
 | |
|     // Write the generated bindings to an output file.
 | |
|     bindings.write_to_file(BINDGEN_OUTPUT_FILE).unwrap();
 | |
| 
 | |
|     Ok(())
 | |
| }
 | |
| 
 | |
| /// Use cbindgen to generate C-header's for Rust static libraries.
 | |
| fn execute_cbindgen() -> Result<(), &'static str> {
 | |
|     let crate_dir = env::var("CARGO_MANIFEST_DIR").or(Err("CARGO_MANIFEST_DIR not specified"))?;
 | |
|     let build_dir = PathBuf::from(env::var("CARGO_TARGET_DIR").unwrap_or_else(|_| ".".into()));
 | |
|     let outfile_path = build_dir.join(C_HEADER_OUTPUT);
 | |
| 
 | |
|     // Useful for build diagnostics
 | |
|     eprintln!("cbindgen outputting {:?}", &outfile_path);
 | |
|     cbindgen::generate(crate_dir)
 | |
|         .expect("Unable to generate bindings")
 | |
|         .write_to_file(&outfile_path);
 | |
| 
 | |
|     Ok(())
 | |
| }
 | |
| 
 | |
| fn detect_clamav_build() -> Result<(), &'static str> {
 | |
|     println!("cargo:rerun-if-env-changed=LIBCLAMAV");
 | |
| 
 | |
|     if search_and_link_lib("LIBCLAMAV")? {
 | |
|         eprintln!("NOTE: LIBCLAMAV defined. Examining LIB* environment variables");
 | |
|         // Need to link with libclamav dependencies
 | |
|         for var in LIB_ENV_LINK {
 | |
|             let _ = search_and_link_lib(var);
 | |
|         }
 | |
| 
 | |
|         if cfg!(windows) {
 | |
|             for var in LIB_ENV_LINK_WINDOWS {
 | |
|                 let _ = search_and_link_lib(var);
 | |
|             }
 | |
|             for lib in LIB_LINK_WINDOWS {
 | |
|                 println!("cargo:rustc-link-lib={}", lib);
 | |
|             }
 | |
|         } else {
 | |
|             // Link the test executable with libstdc++ on unix systems,
 | |
|             // This is needed for fully-static build where clamav & 3rd party
 | |
|             // dependencies excluding the std libs are static.
 | |
|             if cfg!(target_os = "linux") {
 | |
|                 eprintln!("NOTE: linking libstdc++ (linux target)");
 | |
|                 println!("cargo:rustc-link-lib=stdc++");
 | |
|             } else {
 | |
|                 eprintln!("NOTE: NOT linking libstdc++ (non-linux target)");
 | |
|             }
 | |
|         }
 | |
|     } else {
 | |
|         println!("NOTE: LIBCLAMAV not defined");
 | |
|     }
 | |
| 
 | |
|     Ok(())
 | |
| }
 | |
| 
 | |
| //
 | |
| // Return whether the specified environment variable has been set, and output
 | |
| // linking directives as a side-effect
 | |
| //
 | |
| fn search_and_link_lib(environment_variable: &str) -> Result<bool, &'static str> {
 | |
|     eprintln!("  - checking for {:?} in environment", environment_variable);
 | |
|     let filepath_str = match env::var(environment_variable) {
 | |
|         Err(env::VarError::NotPresent) => return Ok(false),
 | |
|         Err(env::VarError::NotUnicode(_)) => return Err("environment value not unicode"),
 | |
|         Ok(s) => {
 | |
|             if s.is_empty() {
 | |
|                 return Ok(false);
 | |
|             } else {
 | |
|                 s
 | |
|             }
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     let parsed_path = parse_lib_path(&filepath_str)?;
 | |
|     eprintln!(
 | |
|         "  - adding {:?} to rustc library search path",
 | |
|         &parsed_path.dir
 | |
|     );
 | |
|     println!("cargo:rustc-link-search={}", parsed_path.dir);
 | |
|     eprintln!("  - requesting that rustc link {:?}", &parsed_path.libname);
 | |
|     println!("cargo:rustc-link-lib={}", parsed_path.libname);
 | |
| 
 | |
|     Ok(true)
 | |
| }
 | |
| 
 | |
| struct ParsedLibraryPath {
 | |
|     dir: String,
 | |
|     libname: String,
 | |
| }
 | |
| 
 | |
| // Parse a library path, returning the portion expected after the `-l`, and the
 | |
| // directory containing the library
 | |
| fn parse_lib_path<'a>(path: &'a str) -> Result<ParsedLibraryPath, &'static str> {
 | |
|     let path = PathBuf::from(path);
 | |
|     let file_name = path
 | |
|         .file_name()
 | |
|         .ok_or("file name not found")?
 | |
|         .to_str()
 | |
|         .ok_or("file name not unicode")?;
 | |
| 
 | |
|     // This can't fail because it came from a &str
 | |
|     let dir = path
 | |
|         .parent()
 | |
|         .unwrap_or_else(|| Path::new("."))
 | |
|         .to_str()
 | |
|         .unwrap()
 | |
|         .to_owned();
 | |
| 
 | |
|     // Grab the portion up to the first '.'
 | |
|     let full_libname = file_name
 | |
|         .split('.')
 | |
|         .next()
 | |
|         .ok_or("no '.' found in file name")?;
 | |
| 
 | |
|     // Windows typically requires the full filename when linking system libraries,
 | |
|     // but not when it's one of the locally-generated libraries.
 | |
|     let should_trim_leading_lib =
 | |
|         !cfg!(windows) || WINDOWS_TRIM_LOCAL_LIB.iter().any(|s| *s == full_libname);
 | |
| 
 | |
|     let libname = if should_trim_leading_lib {
 | |
|         full_libname
 | |
|             .strip_prefix("lib")
 | |
|             .ok_or(r#"file name doesn't begin with "lib""#)?
 | |
|     } else {
 | |
|         full_libname
 | |
|     }
 | |
|     .to_owned();
 | |
| 
 | |
|     Ok(ParsedLibraryPath { dir, libname })
 | |
| }
 |