Skip to content

Commit 4380486

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

File tree

2 files changed

+69
-22
lines changed

2 files changed

+69
-22
lines changed

Diff for: rust/src/enterprise.rs

+7-4
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() {

Diff for: rust/src/headless.rs

+62-18
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,10 @@ use crate::{
1818
};
1919
use std::io;
2020
use std::path::{Path, PathBuf};
21-
use std::sync::atomic::AtomicUsize;
21+
use std::sync::atomic::{AtomicBool, 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;
@@ -33,9 +32,12 @@ use std::thread::JoinHandle;
3332
use std::time::Duration;
3433

3534
static MAIN_THREAD_HANDLE: Mutex<Option<JoinHandle<()>>> = Mutex::new(None);
36-
35+
/// Prevent two threads from calling init() at the same time
36+
static INIT_LOCK: Mutex<()> = Mutex::new(());
3737
/// Used to prevent shutting down Binary Ninja if there are other [`Session`]'s.
3838
static SESSION_COUNT: AtomicUsize = AtomicUsize::new(0);
39+
/// If we checked out a floating license and should release it on shutdown
40+
static NEED_LICENSE_RELEASE: AtomicBool = AtomicBool::new(false);
3941

4042
#[derive(Error, Debug)]
4143
pub enum InitializationError {
@@ -47,6 +49,8 @@ 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,
5054
}
5155

5256
/// Loads plugins, core architecture, platform, etc.
@@ -68,11 +72,14 @@ pub fn init() -> Result<(), InitializationError> {
6872
/// ⚠️ Important! Must be called at the end of scripts. ⚠️
6973
pub fn shutdown() {
7074
match crate::product().as_str() {
71-
"Binary Ninja Enterprise Client" | "Binary Ninja Ultimate" => enterprise::release_license(),
75+
"Binary Ninja Enterprise Client" | "Binary Ninja Ultimate" => {
76+
if NEED_LICENSE_RELEASE.load(SeqCst) {
77+
enterprise::release_license()
78+
}
79+
}
7280
_ => {}
7381
}
7482
unsafe { binaryninjacore_sys::BNShutdown() };
75-
release_license();
7683
// TODO: We might want to drop the main thread here, however that requires getting the handler ctx to drop the sender.
7784
}
7885

@@ -201,7 +208,9 @@ pub fn init_with_opts(options: InitializationOptions) -> Result<(), Initializati
201208
"Binary Ninja Enterprise Client" | "Binary Ninja Ultimate" => {
202209
if options.checkout_license {
203210
// We are allowed to check out a license, so do it!
204-
enterprise::checkout_license(options.floating_license_duration)?;
211+
if enterprise::checkout_license(options.floating_license_duration)? {
212+
NEED_LICENSE_RELEASE.store(true, SeqCst);
213+
}
205214
}
206215
}
207216
_ => {}
@@ -271,26 +280,43 @@ pub fn license_location() -> Option<LicenseLocation> {
271280
if license_path().exists() {
272281
Some(LicenseLocation::File)
273282
} 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,
283+
// Check to see if we might be authorizing with enterprise
284+
if crate::product().as_str() == "Binary Ninja Enterprise Client"
285+
|| crate::product().as_str() == "Binary Ninja Ultimate"
286+
{
287+
// If we can't initialize enterprise, we probably are missing enterprise.server.url
288+
// and our license surely is not valid.
289+
if !enterprise::is_server_initialized() && !enterprise::initialize_server() {
290+
return None;
291+
}
292+
// If Enterprise thinks we are using a floating license, then report it will be in the keychain
293+
if enterprise::is_server_floating_license() {
294+
Some(LicenseLocation::Keychain)
295+
} else {
296+
None
297+
}
298+
} else {
299+
None
278300
}
279301
}
280302
}
281303
}
282304
}
283305

284306
/// 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 {}
307+
pub struct Session {
308+
index: usize,
309+
}
286310

287311
impl Session {
288312
/// Get a registered [`Session`] for use.
289313
///
290314
/// This is required so that we can keep track of the [`SESSION_COUNT`].
291315
fn registered_session() -> Self {
292-
let _previous_count = SESSION_COUNT.fetch_add(1, SeqCst);
293-
Self {}
316+
let previous_count = SESSION_COUNT.fetch_add(1, SeqCst);
317+
Self {
318+
index: previous_count,
319+
}
294320
}
295321

296322
/// Before calling new you must make sure that the license is retrievable, otherwise the core won't be able to initialize.
@@ -300,10 +326,18 @@ impl Session {
300326
pub fn new() -> Result<Self, InitializationError> {
301327
if license_location().is_some() {
302328
// 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.
329+
330+
// Grab the lock before initialization to prevent another thread from initializing
331+
// and racing the call to BNInitPlugins.
332+
let _lock = INIT_LOCK
333+
.lock()
334+
.map_err(|_| InitializationError::InitMutex)?;
305335
let session = Self::registered_session();
306-
init()?;
336+
// Since this whole section is locked, we're guaranteed to be index 0 if we're first.
337+
// Only the first thread hitting this should be allowed to call BNInitPlugins
338+
if session.index == 0 {
339+
init()?;
340+
}
307341
Ok(session)
308342
} else {
309343
// There was no license that could be automatically retrieved, you must call [Self::new_with_license].
@@ -316,8 +350,18 @@ impl Session {
316350
/// This differs from [`Session::new`] in that it does not check to see if there is a license that the core
317351
/// can discover by itself, therefor it is expected that you know where your license is when calling this directly.
318352
pub fn new_with_opts(options: InitializationOptions) -> Result<Self, InitializationError> {
319-
init_with_opts(options)?;
320-
Ok(Self::registered_session())
353+
// Grab the lock before initialization to prevent another thread from initializing
354+
// and racing the call to BNInitPlugins.
355+
let _lock = INIT_LOCK
356+
.lock()
357+
.map_err(|_| InitializationError::InitMutex)?;
358+
let session = Self::registered_session();
359+
// Since this whole section is locked, we're guaranteed to be index 0 if we're first.
360+
// Only the first thread hitting this should be allowed to call BNInitPlugins
361+
if session.index == 0 {
362+
init_with_opts(options)?;
363+
}
364+
Ok(session)
321365
}
322366

323367
/// ```no_run

0 commit comments

Comments
 (0)