Browse code

Generate Rust-bindings for some internal C-headers

Use bindgen to generate Rust-bindings for some libclamav internal
functions and structures in an new "sys.rs" module.

"sys.rs" will be generated at build-time and unfortunately must be
dropped in the source/libclamav_rust/src directory rather than under the
build directory. As far as I know, Cargo/Rust provide no way to set an
include path to the build directory to separately place generated files.
TODO: Verify if this is true and move sys.rs to the build directory if
possible.

Using the new bindings with:
- the logging module.
- the cdiff module.

Also:
- Removed clamav_rust.h from .gitignore, because we generate it in the
build directory now.
- Removed the hand-written bindings from the cbindgen exclusions.
lib.rs has an annotation that prevents cbindgen from looking at sys.rs.
- Fixed a `u8` -> `c_char` type issue in cdiff in the cli_getdsig() call
parameters.

micasnyd authored on 2021/12/21 02:39:40
Showing 9 changed files
... ...
@@ -228,12 +228,13 @@ libclamav/c++/llvm/tools/llvmc/plugins/Base/Base.td
228 228
 debug/
229 229
 target/
230 230
 
231
-# Generated by libclamav_rust/build.rs in IDEs
232
-libclamav_rust/clamav_rust.h
233
-
234 231
 # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
235 232
 # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
236 233
 Cargo.lock
237 234
 
238 235
 # These are backup files generated by rustfmt
239 236
 **/*.rs.bk
237
+
238
+# The name of our generated ClamAV-Rust bindings file.
239
+libclamav_rust/src/sys.rs
240
+
... ...
@@ -856,6 +856,12 @@ inline void cli_dbgmsg(const char *str, ...) __attribute__((format(printf, 1, 2)
856 856
 inline void cli_dbgmsg(const char *str, ...);
857 857
 #endif
858 858
 
859
+#ifdef __GNUC__
860
+void cli_dbgmsg_no_inline(const char *str, ...) __attribute__((format(printf, 1, 2)));
861
+#else
862
+void cli_dbgmsg_no_inline(const char *str, ...);
863
+#endif
864
+
859 865
 #ifdef HAVE_CLI_GETPAGESIZE
860 866
 #undef HAVE_CLI_GETPAGESIZE
861 867
 #endif
... ...
@@ -180,6 +180,14 @@ inline void cli_dbgmsg(const char *str, ...)
180 180
     }
181 181
 }
182 182
 
183
+void cli_dbgmsg_no_inline(const char *str, ...)
184
+{
185
+    if (UNLIKELY(cli_get_debug_flag())) {
186
+        MSGCODE(buff, len, "LibClamAV debug: ");
187
+        fputs(buff, stderr);
188
+    }
189
+}
190
+
183 191
 int cli_matchregex(const char *str, const char *regex)
184 192
 {
185 193
     regex_t reg;
... ...
@@ -19,3 +19,4 @@ name = "clamav_rust"
19 19
 
20 20
 [build-dependencies]
21 21
 cbindgen = "0.20"
22
+bindgen = "0.59"
... ...
@@ -1,6 +1,8 @@
1 1
 use std::env;
2 2
 use std::path::{Path, PathBuf};
3 3
 
4
+use bindgen::builder;
5
+
4 6
 // Note to maintainers: this is currently a hybrid of examination of the
5 7
 // CMake environment, and leaning on Rust `cfg` elements. Ideally, it
6 8
 // should be possible to work in this space (e.g., execute tests from an
... ...
@@ -14,7 +16,7 @@ use std::path::{Path, PathBuf};
14 14
 
15 15
 // A list of environment variables to query to determine additional libraries
16 16
 // that need to be linked to resolve dependencies.
17
-const LIB_ENV_LINK: [&str; 12] = [
17
+const LIB_ENV_LINK: &[&str] = &[
18 18
     "LIBSSL",
19 19
     "LIBCRYPTO",
20 20
     "LIBZ",
... ...
@@ -30,13 +32,47 @@ const LIB_ENV_LINK: [&str; 12] = [
30 30
 ];
31 31
 
32 32
 // The same, but additional values to check on Windows platforms
33
-const LIB_ENV_LINK_WINDOWS: [&str; 2] = ["LIBPTHREADW32", "LIBWIN32COMPAT"];
33
+const LIB_ENV_LINK_WINDOWS: &[&str] = &["LIBPTHREADW32", "LIBWIN32COMPAT"];
34 34
 
35 35
 // Additional [verbatim] libraries to link on Windows platforms
36
-const LIB_LINK_WINDOWS: [&str; 4] = ["wsock32", "ws2_32", "Shell32", "User32"];
36
+const LIB_LINK_WINDOWS: &[&str] = &["wsock32", "ws2_32", "Shell32", "User32"];
37 37
 
38 38
 // Windows library names that must have the leading `lib` trimmed (if encountered)
39
-const WINDOWS_TRIM_LOCAL_LIB: [&str; 2] = ["libclamav", "libclammspack"];
39
+const WINDOWS_TRIM_LOCAL_LIB: &[&str] = &["libclamav", "libclammspack"];
40
+
41
+// Generate bindings for these functions:
42
+const BINDGEN_FUNCTIONS: &[&str] = &[
43
+    "cli_ctx",
44
+    "cli_warnmsg",
45
+    "cli_dbgmsg_no_inline",
46
+    "cli_infomsg_simple",
47
+    "cli_errmsg",
48
+    "cli_append_virus",
49
+    "cli_versig2",
50
+    "cli_getdsig",
51
+    "cli_get_debug_flag",
52
+];
53
+
54
+// Generate bindings for these types (structs, enums):
55
+const BINDGEN_TYPES: &[&str] = &["cli_matcher", "cli_ac_data", "cli_ac_result"];
56
+
57
+// Find the required functions and types in these headers:
58
+const BINDGEN_HEADERS: &[&str] = &[
59
+    "../libclamav/matcher.h",
60
+    "../libclamav/matcher-ac.h",
61
+    "../libclamav/others.h",
62
+    "../libclamav/dsig.h",
63
+];
64
+
65
+// Find the required headers in these directories:
66
+const BINDGEN_INCLUDE_PATHS: &[&str] = &[
67
+    "-I../libclamav",
68
+    "-I../libclamunrar_iface",
69
+    "-I../libclammspack",
70
+];
71
+
72
+// Write the bindings to this file:
73
+const BINDGEN_OUTPUT_FILE: &str = "src/sys.rs";
40 74
 
41 75
 const C_HEADER_OUTPUT: &str = "clamav_rust.h";
42 76
 
... ...
@@ -58,14 +94,54 @@ fn main() -> Result<(), &'static str> {
58 58
 
59 59
     // We only want to execute cbindgen for `cargo build`, not `cargo test`.
60 60
     // FindRust.cmake defines $CARGO_CMD so we can differentiate.
61
-    if "build" == env::var("CARGO_CMD").or(Ok("".to_string()))? {
61
+    let cargo_cmd = env::var("CARGO_CMD").unwrap_or("".into());
62
+    if cargo_cmd == "build" {
63
+        execute_bindgen()?;
62 64
         execute_cbindgen()?;
63 65
     } else {
64
-        eprintln!("NOTE: Not performing cbindgen as CARGO_CMD != build");
66
+        eprintln!("NOTE: Not generating bindings because CARGO_CMD != build");
65 67
     }
68
+
69
+    Ok(())
70
+}
71
+
72
+/// Use bindgen to generate Rust bindings to call into C libraries.
73
+fn execute_bindgen() -> Result<(), &'static str> {
74
+    let build_dir = PathBuf::from(env::var("CARGO_TARGET_DIR").unwrap_or(".".into()));
75
+    let build_include_path = format!("-I{}", build_dir.join("..").to_str().unwrap());
76
+
77
+    // Configure and generate bindings.
78
+    let mut builder = builder()
79
+        // Silence code-style warnings for generated bindings.
80
+        .raw_line("#![allow(non_snake_case, non_camel_case_types, non_upper_case_globals)]")
81
+        // Make the bindings pretty.
82
+        .rustfmt_bindings(true)
83
+        // Enable bindgen to find generated headers in the build directory, too.
84
+        .clang_arg(build_include_path);
85
+
86
+    for &include_path in BINDGEN_INCLUDE_PATHS {
87
+        builder = builder.clang_arg(include_path);
88
+    }
89
+    for &header in BINDGEN_HEADERS {
90
+        builder = builder.header(header);
91
+    }
92
+    for &c_function in BINDGEN_FUNCTIONS {
93
+        builder = builder.allowlist_function(c_function);
94
+    }
95
+    for &c_type in BINDGEN_TYPES {
96
+        builder = builder.allowlist_type(c_type);
97
+    }
98
+
99
+    // Generate!
100
+    let bindings = builder.generate().unwrap();
101
+
102
+    // Write the generated bindings to an output file.
103
+    bindings.write_to_file(BINDGEN_OUTPUT_FILE).unwrap();
104
+
66 105
     Ok(())
67 106
 }
68 107
 
108
+/// Use cbindgen to generate C-header's for Rust static libraries.
69 109
 fn execute_cbindgen() -> Result<(), &'static str> {
70 110
     let crate_dir = env::var("CARGO_MANIFEST_DIR").or(Err("CARGO_MANIFEST_DIR not specified"))?;
71 111
     let build_dir = PathBuf::from(env::var("CARGO_TARGET_DIR").unwrap_or(".".into()));
... ...
@@ -86,15 +162,15 @@ fn detect_clamav_build() -> Result<(), &'static str> {
86 86
     if search_and_link_lib("LIBCLAMAV")? {
87 87
         eprintln!("NOTE: LIBCLAMAV defined. Examining LIB* environment variables");
88 88
         // Need to link with libclamav dependencies
89
-        for var in &LIB_ENV_LINK {
89
+        for var in LIB_ENV_LINK {
90 90
             let _ = search_and_link_lib(var);
91 91
         }
92 92
 
93 93
         if cfg!(windows) {
94
-            for var in &LIB_ENV_LINK_WINDOWS {
94
+            for var in LIB_ENV_LINK_WINDOWS {
95 95
                 let _ = search_and_link_lib(var);
96 96
             }
97
-            for lib in &LIB_LINK_WINDOWS {
97
+            for lib in LIB_LINK_WINDOWS {
98 98
                 println!("cargo:rustc-link-lib={}", lib);
99 99
             }
100 100
         } else {
... ...
@@ -17,15 +17,8 @@ after_includes = ""
17 17
 
18 18
 [export]
19 19
 include = ["cdiff_apply", "script2cdiff"]
20
-exclude = [
21
-    "cli_dbgmsg",
22
-    "cli_errmsg",
23
-    "cli_infomsg_simple",
24
-    "cli_warnmsg",
25
-    "cli_versig2",
26
-    "cli_get_debug_flag",
27
-    "cli_getdsig",
28
-]
20
+exclude = []
21
+
29 22
 # prefix = "CAPI_"
30 23
 item_types = []
31 24
 renaming_overrides_prefixing = false
... ...
@@ -24,7 +24,7 @@ use std::{
24 24
     fs::{self, File, OpenOptions},
25 25
     io::{prelude::*, BufReader, BufWriter, Read, Seek, SeekFrom, Write},
26 26
     iter::*,
27
-    os::raw::{c_char, c_uchar},
27
+    os::raw::c_char,
28 28
     process,
29 29
 };
30 30
 
... ...
@@ -34,6 +34,8 @@ use log::{debug, error, warn};
34 34
 use sha2::{Digest, Sha256};
35 35
 use thiserror::Error;
36 36
 
37
+use crate::sys;
38
+
37 39
 /// Maximum size of a digital signature
38 40
 const MAX_SIG_SIZE: usize = 350;
39 41
 
... ...
@@ -255,28 +257,9 @@ impl<'a> XchgOp<'a> {
255 255
     }
256 256
 }
257 257
 
258
-extern "C" {
259
-    fn cli_versig2(
260
-        digest: *const c_uchar,
261
-        dsig: *const c_char,
262
-        n: *const c_char,
263
-        e: *const c_char,
264
-    ) -> i32;
265
-
266
-    fn cli_getdsig(
267
-        host: *const u8,
268
-        user: *const u8,
269
-        data: *const u8,
270
-        datalen: u32,
271
-        mode: u8,
272
-    ) -> *const c_char;
273
-
274
-    fn cli_get_debug_flag() -> u8;
275
-}
276
-
277 258
 fn is_debug_enabled() -> bool {
278 259
     unsafe {
279
-        let debug_flag = cli_get_debug_flag();
260
+        let debug_flag = sys::cli_get_debug_flag();
280 261
         // Return true if debug_flag is not 0
281 262
         !matches!(debug_flag, 0)
282 263
     }
... ...
@@ -418,9 +401,9 @@ pub fn script2cdiff(script_file_name: &str, builder: &str, server: &str) -> Resu
418 418
         // These strings should not contain interior NULs
419 419
         let server = CString::new(server).unwrap();
420 420
         let builder = CString::new(builder).unwrap();
421
-        let dsig_ptr = cli_getdsig(
422
-            server.as_c_str().as_ptr() as *const u8,
423
-            builder.as_c_str().as_ptr() as *const u8,
421
+        let dsig_ptr = sys::cli_getdsig(
422
+            server.as_c_str().as_ptr() as *const c_char,
423
+            builder.as_c_str().as_ptr() as *const c_char,
424 424
             sha256.to_vec().as_ptr(),
425 425
             32,
426 426
             2,
... ...
@@ -511,7 +494,7 @@ pub fn cdiff_apply(file: &mut File, mode: ApplyMode) -> Result<(), CdiffError> {
511 511
             let n = CString::new(PUBLIC_KEY_MODULUS).unwrap();
512 512
             let e = CString::new(PUBLIC_KEY_EXPONENT).unwrap();
513 513
             let versig_result = unsafe {
514
-                cli_versig2(
514
+                sys::cli_versig2(
515 515
                     sha256.to_vec().as_ptr(),
516 516
                     dsig_cstring.as_ptr(),
517 517
                     n.as_ptr() as *const c_char,
... ...
@@ -20,6 +20,9 @@
20 20
  *  MA 02110-1301, USA.
21 21
  */
22 22
 
23
+/// cbindgen:ignore
24
+pub mod sys;
25
+
23 26
 pub mod cdiff;
24 27
 pub mod logging;
25 28
 pub mod util;
... ...
@@ -21,16 +21,10 @@
21 21
  */
22 22
 
23 23
 use std::ffi::CString;
24
-use std::os::raw::c_char;
25 24
 
26 25
 use log::{set_max_level, Level, LevelFilter, Metadata, Record};
27 26
 
28
-extern "C" {
29
-    fn cli_warnmsg(str: *const c_char, ...) -> ();
30
-    fn cli_dbgmsg(str: *const c_char, ...) -> ();
31
-    fn cli_infomsg_simple(str: *const c_char, ...) -> ();
32
-    fn cli_errmsg(str: *const c_char, ...) -> ();
33
-}
27
+use crate::sys;
34 28
 
35 29
 pub struct ClamLogger;
36 30
 
... ...
@@ -46,16 +40,16 @@ impl log::Log for ClamLogger {
46 46
 
47 47
             match record.level() {
48 48
                 Level::Debug => unsafe {
49
-                    cli_dbgmsg(ptr);
49
+                    sys::cli_dbgmsg_no_inline(ptr);
50 50
                 },
51 51
                 Level::Error => unsafe {
52
-                    cli_errmsg(ptr);
52
+                    sys::cli_errmsg(ptr);
53 53
                 },
54 54
                 Level::Info => unsafe {
55
-                    cli_infomsg_simple(ptr);
55
+                    sys::cli_infomsg_simple(ptr);
56 56
                 },
57 57
                 Level::Warn => unsafe {
58
-                    cli_warnmsg(ptr);
58
+                    sys::cli_warnmsg(ptr);
59 59
                 },
60 60
                 _ => {}
61 61
             }