Skip to content

Commit 3437052

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

File tree

2 files changed

+55
-22
lines changed

2 files changed

+55
-22
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: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,13 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
use crate::{
16-
binary_view, bundled_plugin_directory, enterprise, is_license_validated, is_main_thread,
17-
license_path, set_bundled_plugin_directory, set_license, string::IntoJson,
18-
};
15+
use crate::{binary_view, bundled_plugin_directory, enterprise, is_license_validated, is_main_thread, license_path, product, set_bundled_plugin_directory, set_license, string::IntoJson};
1916
use std::io;
2017
use std::path::{Path, PathBuf};
2118
use std::sync::atomic::AtomicUsize;
2219
use std::sync::atomic::Ordering::SeqCst;
2320
use thiserror::Error;
2421

25-
use crate::enterprise::release_license;
2622
use crate::main_thread::{MainThreadAction, MainThreadHandler};
2723
use crate::progress::ProgressCallback;
2824
use crate::rc::Ref;
@@ -32,11 +28,14 @@ use std::sync::Mutex;
3228
use std::thread::JoinHandle;
3329
use std::time::Duration;
3430

31+
static INIT_LOCK: Mutex<()> = Mutex::new(());
3532
static MAIN_THREAD_HANDLE: Mutex<Option<JoinHandle<()>>> = Mutex::new(None);
3633

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

37+
static NEED_LICENSE_RELEASE: Mutex<bool> = Mutex::new(false);
38+
4039
#[derive(Error, Debug)]
4140
pub enum InitializationError {
4241
#[error("main thread could not be started: {0}")]
@@ -47,6 +46,8 @@ pub enum InitializationError {
4746
InvalidLicense,
4847
#[error("no license could located, please see `binaryninja::set_license` for details")]
4948
NoLicenseFound,
49+
#[error("could not acquire initialization mutex")]
50+
Mutex,
5051
}
5152

5253
/// Loads plugins, core architecture, platform, etc.
@@ -68,11 +69,16 @@ pub fn init() -> Result<(), InitializationError> {
6869
/// ⚠️ Important! Must be called at the end of scripts. ⚠️
6970
pub fn shutdown() {
7071
match crate::product().as_str() {
71-
"Binary Ninja Enterprise Client" | "Binary Ninja Ultimate" => enterprise::release_license(),
72+
"Binary Ninja Enterprise Client" | "Binary Ninja Ultimate" => {
73+
if let Ok(need_release) = NEED_LICENSE_RELEASE.lock() {
74+
if *need_release {
75+
enterprise::release_license()
76+
}
77+
}
78+
}
7279
_ => {}
7380
}
7481
unsafe { binaryninjacore_sys::BNShutdown() };
75-
release_license();
7682
// TODO: We might want to drop the main thread here, however that requires getting the handler ctx to drop the sender.
7783
}
7884

@@ -201,7 +207,9 @@ pub fn init_with_opts(options: InitializationOptions) -> Result<(), Initializati
201207
"Binary Ninja Enterprise Client" | "Binary Ninja Ultimate" => {
202208
if options.checkout_license {
203209
// We are allowed to check out a license, so do it!
204-
enterprise::checkout_license(options.floating_license_duration)?;
210+
if enterprise::checkout_license(options.floating_license_duration)? {
211+
*(NEED_LICENSE_RELEASE.lock().map_err(|_| InitializationError::InvalidLicense)?) = true;
212+
}
205213
}
206214
}
207215
_ => {}
@@ -271,26 +279,39 @@ pub fn license_location() -> Option<LicenseLocation> {
271279
if license_path().exists() {
272280
Some(LicenseLocation::File)
273281
} 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,
282+
// Check to see if we might be authorizing with enterprise
283+
if product().as_str() == "Binary Ninja Enterprise Client" || product().as_str() == "Binary Ninja Ultimate" {
284+
if !enterprise::is_server_initialized() && !enterprise::initialize_server() {
285+
return None;
286+
}
287+
// If Enterprise thinks we are using a floating license, then report it will be in the keychain
288+
if enterprise::is_server_floating_license() {
289+
Some(LicenseLocation::Keychain)
290+
} else {
291+
None
292+
}
293+
} else {
294+
None
278295
}
279296
}
280297
}
281298
}
282299
}
283300

284301
/// 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 {}
302+
pub struct Session {
303+
index: usize,
304+
}
286305

287306
impl Session {
288307
/// Get a registered [`Session`] for use.
289308
///
290309
/// This is required so that we can keep track of the [`SESSION_COUNT`].
291310
fn registered_session() -> Self {
292-
let _previous_count = SESSION_COUNT.fetch_add(1, SeqCst);
293-
Self {}
311+
let previous_count = SESSION_COUNT.fetch_add(1, SeqCst);
312+
Self {
313+
index: previous_count,
314+
}
294315
}
295316

296317
/// Before calling new you must make sure that the license is retrievable, otherwise the core won't be able to initialize.
@@ -302,8 +323,12 @@ impl Session {
302323
// We were able to locate a license, continue with initialization.
303324
// Grab the session before initialization to prevent another thread from initializing
304325
// and shutting down before this thread can increment the SESSION_COUNT.
326+
let lock = INIT_LOCK.lock().map_err(|_| InitializationError::Mutex)?;
305327
let session = Self::registered_session();
306-
init()?;
328+
if session.index == 0 {
329+
init()?;
330+
}
331+
drop(lock);
307332
Ok(session)
308333
} else {
309334
// There was no license that could be automatically retrieved, you must call [Self::new_with_license].
@@ -316,8 +341,13 @@ impl Session {
316341
/// This differs from [`Session::new`] in that it does not check to see if there is a license that the core
317342
/// can discover by itself, therefor it is expected that you know where your license is when calling this directly.
318343
pub fn new_with_opts(options: InitializationOptions) -> Result<Self, InitializationError> {
319-
init_with_opts(options)?;
320-
Ok(Self::registered_session())
344+
let lock = INIT_LOCK.lock().map_err(|_| InitializationError::Mutex)?;
345+
let session = Self::registered_session();
346+
if session.index == 0 {
347+
init_with_opts(options)?;
348+
}
349+
drop(lock);
350+
Ok(session)
321351
}
322352

323353
/// ```no_run

0 commit comments

Comments
 (0)