Skip to content

Commit 7759833

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

File tree

2 files changed

+78
-20
lines changed

2 files changed

+78
-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: 71 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,43 @@ 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"
296+
|| crate::product().as_str() == "Binary Ninja Ultimate"
297+
{
298+
// If we can't initialize enterprise, we probably are missing enterprise.server.url
299+
// and our license surely is not valid.
300+
if !enterprise::is_server_initialized() && !enterprise::initialize_server() {
301+
return None;
302+
}
303+
// If Enterprise thinks we are using a floating license, then report it will be in the keychain
304+
if enterprise::is_server_floating_license() {
305+
Some(LicenseLocation::Keychain)
306+
} else {
307+
None
308+
}
309+
} else {
310+
None
278311
}
279312
}
280313
}
281314
}
282315
}
283316

284317
/// 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 {}
318+
pub struct Session {
319+
index: usize,
320+
}
286321

287322
impl Session {
288323
/// Get a registered [`Session`] for use.
289324
///
290325
/// This is required so that we can keep track of the [`SESSION_COUNT`].
291326
fn registered_session() -> Self {
292-
let _previous_count = SESSION_COUNT.fetch_add(1, SeqCst);
293-
Self {}
327+
let previous_count = SESSION_COUNT.fetch_add(1, SeqCst);
328+
Self {
329+
index: previous_count,
330+
}
294331
}
295332

296333
/// Before calling new you must make sure that the license is retrievable, otherwise the core won't be able to initialize.
@@ -300,10 +337,18 @@ impl Session {
300337
pub fn new() -> Result<Self, InitializationError> {
301338
if license_location().is_some() {
302339
// 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.
340+
341+
// Grab the lock before initialization to prevent another thread from initializing
342+
// and racing the call to BNInitPlugins.
343+
let _lock = INIT_LOCK
344+
.lock()
345+
.map_err(|_| InitializationError::InitMutex)?;
305346
let session = Self::registered_session();
306-
init()?;
347+
// Since this whole section is locked, we're guaranteed to be index 0 if we're first.
348+
// Only the first thread hitting this should be allowed to call BNInitPlugins
349+
if session.index == 0 {
350+
init()?;
351+
}
307352
Ok(session)
308353
} else {
309354
// There was no license that could be automatically retrieved, you must call [Self::new_with_license].
@@ -316,8 +361,18 @@ impl Session {
316361
/// This differs from [`Session::new`] in that it does not check to see if there is a license that the core
317362
/// can discover by itself, therefor it is expected that you know where your license is when calling this directly.
318363
pub fn new_with_opts(options: InitializationOptions) -> Result<Self, InitializationError> {
319-
init_with_opts(options)?;
320-
Ok(Self::registered_session())
364+
// Grab the lock before initialization to prevent another thread from initializing
365+
// and racing the call to BNInitPlugins.
366+
let _lock = INIT_LOCK
367+
.lock()
368+
.map_err(|_| InitializationError::InitMutex)?;
369+
let session = Self::registered_session();
370+
// Since this whole section is locked, we're guaranteed to be index 0 if we're first.
371+
// Only the first thread hitting this should be allowed to call BNInitPlugins
372+
if session.index == 0 {
373+
init_with_opts(options)?;
374+
}
375+
Ok(session)
321376
}
322377

323378
/// ```no_run

0 commit comments

Comments
 (0)