Skip to content

Commit 3253533

Browse files
committed
Rust: Better support for floating licenses
1 parent 8093106 commit 3253533

File tree

2 files changed

+72
-20
lines changed

2 files changed

+72
-20
lines changed

rust/src/enterprise.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@ pub enum EnterpriseCheckoutError {
2020
}
2121

2222
/// Initialize the enterprise server connection to check out a floating license.
23-
pub fn checkout_license(duration: Duration) -> Result<(), EnterpriseCheckoutError> {
23+
/// Result value is if we actually checked out a license (i.e. Ok(false) means we already have a
24+
/// license checked out and will not need to release it later)
25+
pub fn checkout_license(duration: Duration) -> Result<bool, EnterpriseCheckoutError> {
2426
if crate::is_ui_enabled() {
2527
// We only need to check out a license if running headlessly.
26-
return Ok(());
28+
return Ok(false);
2729
}
2830

2931
// The disparate core functions we call here might already have mutexes to guard.
@@ -72,9 +74,10 @@ pub fn checkout_license(duration: Duration) -> Result<(), EnterpriseCheckoutErro
7274
last_error,
7375
));
7476
}
77+
Ok(true)
78+
} else {
79+
Ok(false)
7580
}
76-
77-
Ok(())
7881
}
7982

8083
pub fn release_license() {

rust/src/headless.rs

Lines changed: 65 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ use std::sync::atomic::AtomicUsize;
2222
use std::sync::atomic::Ordering::SeqCst;
2323
use thiserror::Error;
2424

25-
use crate::enterprise::release_license;
2625
use crate::main_thread::{MainThreadAction, MainThreadHandler};
2726
use crate::progress::ProgressCallback;
2827
use crate::rc::Ref;
@@ -32,11 +31,14 @@ use std::sync::Mutex;
3231
use std::thread::JoinHandle;
3332
use std::time::Duration;
3433

34+
static INIT_LOCK: Mutex<()> = Mutex::new(());
3535
static MAIN_THREAD_HANDLE: Mutex<Option<JoinHandle<()>>> = Mutex::new(None);
3636

3737
/// Used to prevent shutting down Binary Ninja if there are other [`Session`]'s.
3838
static SESSION_COUNT: AtomicUsize = AtomicUsize::new(0);
3939

40+
static NEED_LICENSE_RELEASE: Mutex<bool> = Mutex::new(false);
41+
4042
#[derive(Error, Debug)]
4143
pub enum InitializationError {
4244
#[error("main thread could not be started: {0}")]
@@ -47,6 +49,10 @@ pub enum InitializationError {
4749
InvalidLicense,
4850
#[error("no license could located, please see `binaryninja::set_license` for details")]
4951
NoLicenseFound,
52+
#[error("could not acquire initialization mutex")]
53+
InitMutex,
54+
#[error("could not acquire enterprise checkout status mutex")]
55+
EnterpriseMutex,
5056
}
5157

5258
/// Loads plugins, core architecture, platform, etc.
@@ -68,11 +74,16 @@ pub fn init() -> Result<(), InitializationError> {
6874
/// ⚠️ Important! Must be called at the end of scripts. ⚠️
6975
pub fn shutdown() {
7076
match crate::product().as_str() {
71-
"Binary Ninja Enterprise Client" | "Binary Ninja Ultimate" => enterprise::release_license(),
77+
"Binary Ninja Enterprise Client" | "Binary Ninja Ultimate" => {
78+
if let Ok(need_release) = NEED_LICENSE_RELEASE.lock() {
79+
if *need_release {
80+
enterprise::release_license()
81+
}
82+
}
83+
}
7284
_ => {}
7385
}
7486
unsafe { binaryninjacore_sys::BNShutdown() };
75-
release_license();
7687
// TODO: We might want to drop the main thread here, however that requires getting the handler ctx to drop the sender.
7788
}
7889

@@ -201,7 +212,16 @@ pub fn init_with_opts(options: InitializationOptions) -> Result<(), Initializati
201212
"Binary Ninja Enterprise Client" | "Binary Ninja Ultimate" => {
202213
if options.checkout_license {
203214
// We are allowed to check out a license, so do it!
204-
enterprise::checkout_license(options.floating_license_duration)?;
215+
if enterprise::checkout_license(options.floating_license_duration)? {
216+
if let Ok(mut need_release) = NEED_LICENSE_RELEASE.lock() {
217+
*need_release = true;
218+
} else {
219+
// Error trying to set this var, but we have checked out a license!
220+
// Need to release it before returning an error, to be consistent
221+
enterprise::release_license();
222+
return Err(InitializationError::EnterpriseMutex);
223+
}
224+
}
205225
}
206226
}
207227
_ => {}
@@ -271,26 +291,41 @@ pub fn license_location() -> Option<LicenseLocation> {
271291
if license_path().exists() {
272292
Some(LicenseLocation::File)
273293
} else {
274-
// Check to see if we might be authorizing with keychain credentials.
275-
match std::env::var("BN_ENTERPRISE_USERNAME") {
276-
Ok(_) => Some(LicenseLocation::Keychain),
277-
Err(_) => None,
294+
// Check to see if we might be authorizing with enterprise
295+
if crate::product().as_str() == "Binary Ninja Enterprise Client" || crate::product().as_str() == "Binary Ninja Ultimate" {
296+
// If we can't initialize enterprise, we probably are missing enterprise.server.url
297+
// and our license surely is not valid.
298+
if !enterprise::is_server_initialized() && !enterprise::initialize_server() {
299+
return None;
300+
}
301+
// If Enterprise thinks we are using a floating license, then report it will be in the keychain
302+
if enterprise::is_server_floating_license() {
303+
Some(LicenseLocation::Keychain)
304+
} else {
305+
None
306+
}
307+
} else {
308+
None
278309
}
279310
}
280311
}
281312
}
282313
}
283314

284315
/// Wrapper for [`init`] and [`shutdown`]. Instantiating this at the top of your script will initialize everything correctly and then clean itself up at exit as well.
285-
pub struct Session {}
316+
pub struct Session {
317+
index: usize,
318+
}
286319

287320
impl Session {
288321
/// Get a registered [`Session`] for use.
289322
///
290323
/// This is required so that we can keep track of the [`SESSION_COUNT`].
291324
fn registered_session() -> Self {
292-
let _previous_count = SESSION_COUNT.fetch_add(1, SeqCst);
293-
Self {}
325+
let previous_count = SESSION_COUNT.fetch_add(1, SeqCst);
326+
Self {
327+
index: previous_count,
328+
}
294329
}
295330

296331
/// Before calling new you must make sure that the license is retrievable, otherwise the core won't be able to initialize.
@@ -300,10 +335,16 @@ impl Session {
300335
pub fn new() -> Result<Self, InitializationError> {
301336
if license_location().is_some() {
302337
// We were able to locate a license, continue with initialization.
303-
// Grab the session before initialization to prevent another thread from initializing
304-
// and shutting down before this thread can increment the SESSION_COUNT.
338+
339+
// Grab the lock before initialization to prevent another thread from initializing
340+
// and racing the call to BNInitPlugins.
341+
let _lock = INIT_LOCK.lock().map_err(|_| InitializationError::InitMutex)?;
305342
let session = Self::registered_session();
306-
init()?;
343+
// Since this whole section is locked, we're guaranteed to be index 0 if we're first.
344+
// Only the first thread hitting this should be allowed to call BNInitPlugins
345+
if session.index == 0 {
346+
init()?;
347+
}
307348
Ok(session)
308349
} else {
309350
// There was no license that could be automatically retrieved, you must call [Self::new_with_license].
@@ -316,8 +357,16 @@ impl Session {
316357
/// This differs from [`Session::new`] in that it does not check to see if there is a license that the core
317358
/// can discover by itself, therefor it is expected that you know where your license is when calling this directly.
318359
pub fn new_with_opts(options: InitializationOptions) -> Result<Self, InitializationError> {
319-
init_with_opts(options)?;
320-
Ok(Self::registered_session())
360+
// Grab the lock before initialization to prevent another thread from initializing
361+
// and racing the call to BNInitPlugins.
362+
let _lock = INIT_LOCK.lock().map_err(|_| InitializationError::InitMutex)?;
363+
let session = Self::registered_session();
364+
// Since this whole section is locked, we're guaranteed to be index 0 if we're first.
365+
// Only the first thread hitting this should be allowed to call BNInitPlugins
366+
if session.index == 0 {
367+
init_with_opts(options)?;
368+
}
369+
Ok(session)
321370
}
322371

323372
/// ```no_run

0 commit comments

Comments
 (0)