Skip to content

Commit d0379d3

Browse files
committed
Improve headless initialization in Rust API
- Add option to disable loading of user and repo plugins - Prevent shutting down the core while other sessions are still running The reason we need to prevent shutting down the core is if one session is dropped and another session has worker actions queued than they would be cleared prematurely, and also the enterprise license would be revoked prematurely as well.
1 parent f5b7f5b commit d0379d3

File tree

2 files changed

+57
-7
lines changed

2 files changed

+57
-7
lines changed

rust/src/headless.rs

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ use crate::{
1818
};
1919
use std::io;
2020
use std::path::{Path, PathBuf};
21+
use std::sync::atomic::AtomicUsize;
22+
use std::sync::atomic::Ordering::SeqCst;
2123
use thiserror::Error;
2224

2325
use crate::enterprise::release_license;
@@ -32,6 +34,9 @@ use std::time::Duration;
3234

3335
static MAIN_THREAD_HANDLE: Mutex<Option<JoinHandle<()>>> = Mutex::new(None);
3436

37+
/// Used to prevent shutting down Binary Ninja if there are other [`Session`]'s.
38+
static SESSION_COUNT: AtomicUsize = AtomicUsize::new(0);
39+
3540
#[derive(Error, Debug)]
3641
pub enum InitializationError {
3742
#[error("main thread could not be started: {0}")]
@@ -91,13 +96,29 @@ pub struct InitializationOptions {
9196
pub floating_license_duration: Duration,
9297
/// The bundled plugin directory to use.
9398
pub bundled_plugin_directory: PathBuf,
99+
/// Whether to initialize user plugins.
100+
///
101+
/// Set this to false if your use might be impacted by a user installed plugin.
102+
pub user_plugins: bool,
103+
/// Whether to initialize repo plugins.
104+
///
105+
/// Set this to false if your use might be impacted by a repo installed plugin.
106+
pub repo_plugins: bool,
94107
}
95108

96109
impl InitializationOptions {
97110
pub fn new() -> Self {
98111
Self::default()
99112
}
100113

114+
pub fn minimal() -> Self {
115+
Self {
116+
user_plugins: false,
117+
repo_plugins: false,
118+
..Self::default()
119+
}
120+
}
121+
101122
/// A license to override with, you can use this to make sure you initialize with a specific license.
102123
///
103124
/// This takes the form of a JSON array. The string should be formed like:
@@ -112,7 +133,7 @@ impl InitializationOptions {
112133
/// If you need to make sure that you do not check out a license set this to false.
113134
///
114135
/// This is really only useful if you have a headless license but are using an enterprise enabled core.
115-
pub fn with_checkout_license(mut self, should_checkout: bool) -> Self {
136+
pub fn with_license_checkout(mut self, should_checkout: bool) -> Self {
116137
self.checkout_license = should_checkout;
117138
self
118139
}
@@ -130,6 +151,18 @@ impl InitializationOptions {
130151
self.floating_license_duration = duration;
131152
self
132153
}
154+
155+
/// Set this to false if your use might be impacted by a user installed plugin.
156+
pub fn with_user_plugins(mut self, should_initialize: bool) -> Self {
157+
self.user_plugins = should_initialize;
158+
self
159+
}
160+
161+
/// Set this to false if your use might be impacted by a repo installed plugin.
162+
pub fn with_repo_plugins(mut self, should_initialize: bool) -> Self {
163+
self.repo_plugins = should_initialize;
164+
self
165+
}
133166
}
134167

135168
impl Default for InitializationOptions {
@@ -141,6 +174,8 @@ impl Default for InitializationOptions {
141174
floating_license_duration: Duration::from_secs(900),
142175
bundled_plugin_directory: bundled_plugin_directory()
143176
.expect("Failed to get bundled plugin directory"),
177+
user_plugins: true,
178+
repo_plugins: true,
144179
}
145180
}
146181
}
@@ -188,8 +223,11 @@ pub fn init_with_opts(options: InitializationOptions) -> Result<(), Initializati
188223
set_bundled_plugin_directory(options.bundled_plugin_directory);
189224

190225
unsafe {
191-
BNInitPlugins(true);
192-
BNInitRepoPlugins();
226+
BNInitPlugins(options.user_plugins);
227+
if options.repo_plugins {
228+
// We are allowed to initialize repo plugins, so do it!
229+
BNInitRepoPlugins();
230+
}
193231
}
194232

195233
if !is_license_validated() {
@@ -249,6 +287,14 @@ pub fn license_location() -> Option<LicenseLocation> {
249287
pub struct Session {}
250288

251289
impl Session {
290+
/// Get a registered [`Session`] for use.
291+
///
292+
/// This is required so that we can keep track of the [`SESSION_COUNT`].
293+
fn registered_session() -> Self {
294+
let _previous_count = SESSION_COUNT.fetch_add(1, SeqCst);
295+
Self {}
296+
}
297+
252298
/// Before calling new you must make sure that the license is retrievable, otherwise the core won't be able to initialize.
253299
///
254300
/// If you cannot otherwise provide a license via `BN_LICENSE_FILE` environment variable or the Binary Ninja user directory
@@ -257,7 +303,7 @@ impl Session {
257303
if license_location().is_some() {
258304
// We were able to locate a license, continue with initialization.
259305
init()?;
260-
Ok(Self {})
306+
Ok(Self::registered_session())
261307
} else {
262308
// There was no license that could be automatically retrieved, you must call [Self::new_with_license].
263309
Err(InitializationError::NoLicenseFound)
@@ -270,7 +316,7 @@ impl Session {
270316
/// can discover by itself, therefor it is expected that you know where your license is when calling this directly.
271317
pub fn new_with_opts(options: InitializationOptions) -> Result<Self, InitializationError> {
272318
init_with_opts(options)?;
273-
Ok(Self {})
319+
Ok(Self::registered_session())
274320
}
275321

276322
/// ```no_run
@@ -364,6 +410,10 @@ impl Session {
364410

365411
impl Drop for Session {
366412
fn drop(&mut self) {
367-
shutdown()
413+
let previous_count = SESSION_COUNT.fetch_sub(1, SeqCst);
414+
if previous_count == 1 {
415+
// We were the last session, therefor we can safely shut down.
416+
shutdown();
417+
}
368418
}
369419
}

rust/tests/initialization.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ fn test_license_validation() {
1616
release_license();
1717
// Make sure we properly report invalid license.
1818
let options = InitializationOptions::default()
19-
.with_checkout_license(false)
19+
.with_license_checkout(false)
2020
.with_license("blah blag");
2121
match init_with_opts(options) {
2222
Ok(_) => panic!("Expected license validation to fail, but it succeeded!"),

0 commit comments

Comments
 (0)