[tor-commits] [tor/master] Add some Rust utility functions and print support

nickm at torproject.org nickm at torproject.org
Fri May 19 13:28:24 UTC 2017


commit f8ef7c65d10ccd9403d9d64ceceba0dd74d6421f
Author: Sebastian Hahn <sebastian at torproject.org>
Date:   Sat Apr 29 09:15:14 2017 +0200

    Add some Rust utility functions and print support
    
    This gives an indication in the log that Tor was built with Rust
    support, as well as laying some groundwork for further string-returning
    APIs to be converted to Rust
---
 Makefile.am                            |   5 +-
 src/common/compat_rust.c               |  39 +++++++++++++
 src/common/compat_rust.h               |  28 +++++++++
 src/common/include.am                  |   6 ++
 src/or/main.c                          |  10 ++++
 src/rust/Cargo.lock                    |  14 +++++
 src/rust/Cargo.toml                    |  14 +++++
 src/rust/include.am                    |   5 ++
 src/rust/tor_util/Cargo.toml           |  13 +++++
 src/rust/tor_util/ffi.rs               |  56 ++++++++++++++++++
 src/rust/tor_util/include.am           |  12 ++++
 src/rust/tor_util/lib.rs               |  13 +++++
 src/rust/tor_util/rust_string.rs       | 101 +++++++++++++++++++++++++++++++++
 src/rust/tor_util/tests/rust_string.rs |  37 ++++++++++++
 src/test/include.am                    |   1 +
 src/test/test.c                        |   1 +
 src/test/test.h                        |   1 +
 src/test/test_rust.c                   |  31 ++++++++++
 18 files changed, 386 insertions(+), 1 deletion(-)

diff --git a/Makefile.am b/Makefile.am
index b8aa049..be4140a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -26,7 +26,7 @@ TESTING_TOR_BINARY=$(top_builddir)/src/or/tor$(EXEEXT)
 endif
 
 if USE_RUST
-rust_ldadd=
+rust_ldadd=$(top_builddir)/src/rust/target/release/libtor_util.a
 else
 rust_ldadd=
 endif
@@ -236,3 +236,6 @@ mostlyclean-local:
 	rm -rf $(HTML_COVER_DIR)
 	rm -rf $(top_builddir)/doc/doxygen
 	rm -rf $(TEST_NETWORK_ALL_LOG_DIR)
+
+clean-local:
+	rm -rf $(top_builddir)/src/rust/target
diff --git a/src/common/compat_rust.c b/src/common/compat_rust.c
new file mode 100644
index 0000000..366fd40
--- /dev/null
+++ b/src/common/compat_rust.c
@@ -0,0 +1,39 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file rust_compat.c
+ * \brief Rust FFI compatibility functions and helpers. This file is only built
+ * if Rust is not used.
+ **/
+
+#include "compat_rust.h"
+#include "util.h"
+
+/**
+ * Free storage pointed to by <b>str</b>, and itself.
+ */
+void
+rust_str_free(rust_str_t str)
+{
+    char *s = (char *)str;
+    tor_free(s);
+}
+
+/**
+ * Return zero-terminated contained string.
+ */
+const char *
+rust_str_get(const rust_str_t str)
+{
+    return (const char *)str;
+}
+
+/* If we were using Rust, we'd say so on startup. */
+rust_str_t
+rust_welcome_string(void)
+{
+    char *s = tor_malloc_zero(1);
+    return (rust_str_t)s;
+}
+
diff --git a/src/common/compat_rust.h b/src/common/compat_rust.h
new file mode 100644
index 0000000..752a29b
--- /dev/null
+++ b/src/common/compat_rust.h
@@ -0,0 +1,28 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file rust_compat.h
+ * \brief Headers for rust_compat.c
+ **/
+
+#ifndef TOR_RUST_COMPAT_H
+#define TOR_RUST_COMPAT_H
+
+#include "torint.h"
+
+/**
+ * Strings allocated in Rust must be freed from Rust code again. Let's make
+ * it less likely to accidentally mess up and call tor_free() on it, because
+ * currently it'll just work but might break at any time.
+ */
+typedef uintptr_t rust_str_t;
+
+void rust_str_free(rust_str_t);
+
+const char *rust_str_get(const rust_str_t);
+
+rust_str_t rust_welcome_string(void);
+
+#endif
+
diff --git a/src/common/include.am b/src/common/include.am
index e285ef5..b37b363 100644
--- a/src/common/include.am
+++ b/src/common/include.am
@@ -100,6 +100,11 @@ LIBOR_A_SRC = \
   $(threads_impl_source)				\
   $(readpassphrase_source)
 
+if USE_RUST
+else
+LIBOR_A_SRC += src/common/compat_rust.c
+endif
+
 src/common/src_common_libor_testing_a-log.$(OBJEXT) \
   src/common/log.$(OBJEXT): micro-revision.i
 
@@ -146,6 +151,7 @@ COMMONHEADERS = \
   src/common/compat.h				\
   src/common/compat_libevent.h			\
   src/common/compat_openssl.h			\
+  src/common/compat_rust.h			\
   src/common/compat_threads.h			\
   src/common/compat_time.h			\
   src/common/compress.h			\
diff --git a/src/or/main.c b/src/or/main.c
index 0da43dc..dcd7ef2 100644
--- a/src/or/main.c
+++ b/src/or/main.c
@@ -58,6 +58,7 @@
 #include "circuitlist.h"
 #include "circuituse.h"
 #include "command.h"
+#include "compat_rust.h"
 #include "compress.h"
 #include "config.h"
 #include "confparse.h"
@@ -3039,6 +3040,15 @@ tor_init(int argc, char *argv[])
                  "Expect more bugs than usual.");
   }
 
+  {
+    rust_str_t rust_str = rust_welcome_string();
+    const char *s = rust_str_get(rust_str);
+    if (strlen(s) > 0) {
+      log_notice(LD_GENERAL, "%s", s);
+    }
+    rust_str_free(rust_str);
+  }
+
   if (network_init()<0) {
     log_err(LD_BUG,"Error initializing network; exiting.");
     return -1;
diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock
new file mode 100644
index 0000000..4ac9606
--- /dev/null
+++ b/src/rust/Cargo.lock
@@ -0,0 +1,14 @@
+[root]
+name = "tor_util"
+version = "0.0.1"
+dependencies = [
+ "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[metadata]
+"checksum libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)" = "babb8281da88cba992fa1f4ddec7d63ed96280a1a53ec9b919fd37b53d71e502"
diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml
new file mode 100644
index 0000000..527c536
--- /dev/null
+++ b/src/rust/Cargo.toml
@@ -0,0 +1,14 @@
+[workspace]
+members = ["tor_util"]
+
+[profile.release]
+debug = true
+panic = "abort"
+
+[source.crates-io]
+registry = 'https://github.com/rust-lang/crates.io-index'
+replace-with = 'vendored-sources'
+
+[source.vendored-sources]
+directory = 'vendor'
+
diff --git a/src/rust/include.am b/src/rust/include.am
new file mode 100644
index 0000000..e198049
--- /dev/null
+++ b/src/rust/include.am
@@ -0,0 +1,5 @@
+include src/rust/tor_util/include.am
+
+EXTRA_DIST +=\
+	src/rust/Cargo.toml \
+	src/rust/Cargo.lock
diff --git a/src/rust/tor_util/Cargo.toml b/src/rust/tor_util/Cargo.toml
new file mode 100644
index 0000000..f175fbd
--- /dev/null
+++ b/src/rust/tor_util/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+authors = ["The Tor Project"]
+name = "tor_util"
+version = "0.0.1"
+
+[lib]
+name = "tor_util"
+path = "lib.rs"
+crate_type = ["rlib", "staticlib"]
+
+[dependencies]
+libc = "*"
+
diff --git a/src/rust/tor_util/ffi.rs b/src/rust/tor_util/ffi.rs
new file mode 100644
index 0000000..af4bfc4
--- /dev/null
+++ b/src/rust/tor_util/ffi.rs
@@ -0,0 +1,56 @@
+//! FFI functions, only to be called from C.
+//!
+//! Equivalent C versions of these live in `src/common/compat_rust.c`
+
+use std::mem::forget;
+use std::ffi::CString;
+
+use libc;
+use rust_string::RustString;
+
+/// Free the passed `RustString` (`rust_str_t` in C), to be used in place of
+/// `tor_free`().
+///
+/// # Examples
+/// ```c
+/// rust_str_t r_s = rust_welcome_string();
+/// rust_str_free(r_s);
+/// ```
+#[no_mangle]
+#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
+pub unsafe extern "C" fn rust_str_free(_str: RustString) {
+    // Empty body: Just drop _str and we're done (Drop takes care of it).
+}
+
+/// Lends an immutable, NUL-terminated C String.
+///
+/// # Examples
+/// ```c
+/// rust_str_t r_s = rust_welcome_string();
+/// const char *s = rust_str_get(r_s);
+/// printf("%s", s);
+/// rust_str_free(r_s);
+/// ```
+#[no_mangle]
+pub unsafe extern "C" fn rust_str_get(str: RustString) -> *const libc::c_char {
+    let res = str.as_ptr();
+    forget(str);
+    res
+}
+
+/// Returns a short string to announce Rust support during startup.
+///
+/// # Examples
+/// ```c
+/// rust_str_t r_s = rust_welcome_string();
+/// const char *s = rust_str_get(r_s);
+/// printf("%s", s);
+/// rust_str_free(r_s);
+/// ```
+#[no_mangle]
+pub extern "C" fn rust_welcome_string() -> RustString {
+    let s = CString::new("Tor is running with Rust integration. Please report \
+                          any bugs you encouter.")
+            .unwrap();
+    RustString::from(s)
+}
diff --git a/src/rust/tor_util/include.am b/src/rust/tor_util/include.am
new file mode 100644
index 0000000..3b87764
--- /dev/null
+++ b/src/rust/tor_util/include.am
@@ -0,0 +1,12 @@
+EXTRA_DIST +=\
+	src/rust/tor_util/Cargo.toml \
+	src/rust/tor_util/lib.rs \
+	src/rust/tor_util/ffi.rs \
+	src/rust/tor_util/rust_string.rs
+
+src/rust/target/release/libtor_util.a: FORCE
+	( cd "$(abs_top_srcdir)/src/rust/tor_util" ; \
+		CARGO_TARGET_DIR="$(abs_top_builddir)/src/rust/target" \
+		$(CARGO) build --release --quiet --frozen )
+
+FORCE:
diff --git a/src/rust/tor_util/lib.rs b/src/rust/tor_util/lib.rs
new file mode 100644
index 0000000..79d583d
--- /dev/null
+++ b/src/rust/tor_util/lib.rs
@@ -0,0 +1,13 @@
+//! C <-> Rust compatibility helpers and types.
+//!
+//! Generically useful, small scale helpers should go here. This goes for both
+//! the C side (in the form of the ffi module) as well as the Rust side
+//! (individual modules per functionality). The corresponding C stuff lives in
+//! `src/common/compat_rust.{c,h}`.
+
+extern crate libc;
+
+mod rust_string;
+pub mod ffi;
+
+pub use rust_string::*;
diff --git a/src/rust/tor_util/rust_string.rs b/src/rust/tor_util/rust_string.rs
new file mode 100644
index 0000000..46ec3fd
--- /dev/null
+++ b/src/rust/tor_util/rust_string.rs
@@ -0,0 +1,101 @@
+use std::ffi::CString;
+use std::mem::forget;
+use libc;
+
+/// Compatibility wrapper for strings allocated in Rust and passed to C.
+///
+/// Rust doesn't ensure the safety of freeing memory across an FFI boundary, so
+/// we need to take special care to ensure we're not accidentally calling
+/// `tor_free`() on any string allocated in Rust. To more easily differentiate
+/// between strings that possibly (if Rust support is enabled) were allocated
+/// in Rust, C has the `rust_str_t` helper type. The equivalent on the Rust
+/// side is `RustString`.
+///
+/// Note: This type must not be used for strings allocated in C.
+#[repr(C)]
+#[derive(Debug)]
+pub struct RustString(*mut libc::c_char);
+
+impl RustString {
+    /// Returns a pointer to the underlying NUL-terminated byte array.
+    ///
+    /// Note that this function is not typically useful for Rust callers,
+    /// except in a direct FFI context.
+    ///
+    /// # Examples
+    /// ```
+    /// # use tor_util::RustString;
+    /// use std::ffi::CString;
+    ///
+    /// let r = RustString::from(CString::new("asdf").unwrap());
+    /// let c_str = r.as_ptr();
+    /// assert_eq!(b'a', unsafe { *c_str as u8});
+    /// ```
+    pub fn as_ptr(&self) -> *const libc::c_char {
+        self.0 as *const libc::c_char
+    }
+}
+
+impl From<CString> for RustString {
+    /// Constructs a new `RustString`
+    ///
+    /// # Examples
+    /// ```
+    /// # use tor_util::RustString;
+    /// use std::ffi::CString;
+    ///
+    /// let r = RustString::from(CString::new("asdf").unwrap());
+    /// ```
+    fn from(str: CString) -> RustString {
+        RustString(str.into_raw())
+    }
+}
+
+impl Into<CString> for RustString {
+    /// Reconstructs a `CString` from this `RustString`.
+    ///
+    /// Useful to take ownership back from a `RustString` that was given to C
+    /// code.
+    ///
+    /// # Examples
+    /// ```
+    /// # use tor_util::RustString;
+    /// use std::ffi::CString;
+    ///
+    /// let cs = CString::new("asdf").unwrap();
+    /// let r = RustString::from(cs.clone());
+    /// let cs2 = r.into();
+    /// assert_eq!(cs, cs2);
+    /// ```
+    fn into(self) -> CString {
+        // Calling from_raw is always OK here: We only construct self using
+        // valid CStrings and don't expose anything that could mutate it
+        let ret = unsafe { CString::from_raw(self.0) };
+        forget(self);
+        ret
+    }
+}
+
+impl Drop for RustString {
+    fn drop(&mut self) {
+        // Don't use into() here, because we would need to move out of
+        // self. Same safety consideration. Immediately drop the created
+        // CString, which takes care of freeing the wrapped string.
+        unsafe { CString::from_raw(self.0) };
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use std::mem;
+    use super::*;
+
+    use libc;
+
+    /// Ensures we're not adding overhead by using RustString.
+    #[test]
+    fn size_of() {
+        assert_eq!(mem::size_of::<*mut libc::c_char>(),
+                   mem::size_of::<RustString>())
+    }
+}
diff --git a/src/rust/tor_util/tests/rust_string.rs b/src/rust/tor_util/tests/rust_string.rs
new file mode 100644
index 0000000..1ff605a
--- /dev/null
+++ b/src/rust/tor_util/tests/rust_string.rs
@@ -0,0 +1,37 @@
+extern crate tor_util;
+extern crate libc;
+
+use std::ffi::CString;
+use tor_util::RustString;
+
+#[test]
+fn rust_string_conversions_preserve_c_string() {
+    let s = CString::new("asdf foo").unwrap();
+    let r = RustString::from(s.clone());
+    let r2 = RustString::from(s.clone());
+    let c = r2.as_ptr();
+    assert_eq!(unsafe { libc::strlen(c) }, 8);
+    let c_str = r.into();
+    assert_eq!(s, c_str);
+}
+
+#[test]
+fn empty_string() {
+    let s = CString::new("").unwrap();
+    let r = RustString::from(s.clone());
+    let c = r.as_ptr();
+    assert_eq!(unsafe { libc::strlen(c) }, 0);
+    let c_str = r.into();
+    assert_eq!(s, c_str);
+}
+
+#[test]
+fn c_string_with_unicode() {
+    // The euro sign is three bytes
+    let s = CString::new("asd€asd").unwrap();
+    let r = RustString::from(s.clone());
+    let c = r.as_ptr();
+    assert_eq!(unsafe { libc::strlen(c) }, 9);
+    let c_str = r.into();
+    assert_eq!(s, c_str);
+}
diff --git a/src/test/include.am b/src/test/include.am
index 8a46587..438eadd 100644
--- a/src/test/include.am
+++ b/src/test/include.am
@@ -130,6 +130,7 @@ src_test_test_SOURCES = \
 	src/test/test_routerkeys.c \
 	src/test/test_routerlist.c \
 	src/test/test_routerset.c \
+	src/test/test_rust.c \
 	src/test/test_scheduler.c \
 	src/test/test_shared_random.c \
 	src/test/test_socks.c \
diff --git a/src/test/test.c b/src/test/test.c
index 4d2cf15..18805cb 100644
--- a/src/test/test.c
+++ b/src/test/test.c
@@ -1233,6 +1233,7 @@ struct testgroup_t testgroups[] = {
   { "routerkeys/", routerkeys_tests },
   { "routerlist/", routerlist_tests },
   { "routerset/" , routerset_tests },
+  { "rust/", rust_tests },
   { "scheduler/", scheduler_tests },
   { "socks/", socks_tests },
   { "shared-random/", sr_tests },
diff --git a/src/test/test.h b/src/test/test.h
index 3d7d05e..3dc1c33 100644
--- a/src/test/test.h
+++ b/src/test/test.h
@@ -231,6 +231,7 @@ extern struct testcase_t router_tests[];
 extern struct testcase_t routerkeys_tests[];
 extern struct testcase_t routerlist_tests[];
 extern struct testcase_t routerset_tests[];
+extern struct testcase_t rust_tests[];
 extern struct testcase_t scheduler_tests[];
 extern struct testcase_t storagedir_tests[];
 extern struct testcase_t socks_tests[];
diff --git a/src/test/test_rust.c b/src/test/test_rust.c
new file mode 100644
index 0000000..6ad57d6
--- /dev/null
+++ b/src/test/test_rust.c
@@ -0,0 +1,31 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#include "orconfig.h"
+#include "compat_rust.h"
+#include "test.h"
+#include "util.h"
+
+static void
+test_welcome_string(void *arg)
+{
+  (void)arg;
+  rust_str_t s = rust_welcome_string();
+  const char *c_str = rust_str_get(s);
+  tt_assert(c_str);
+  size_t len = strlen(c_str);
+#ifdef HAVE_RUST
+  tt_assert(len > 0);
+#else
+  tt_assert(len == 0);
+#endif
+
+ done:
+  rust_str_free(s);
+}
+
+struct testcase_t rust_tests[] = {
+  { "welcome_string", test_welcome_string, 0, NULL, NULL },
+  END_OF_TESTCASES
+};
+





More information about the tor-commits mailing list