1414
1515use crate :: {
1616 binary_view, bundled_plugin_directory, enterprise, is_license_validated, is_main_thread,
17- license_path, set_bundled_plugin_directory, set_license, string:: IntoJson ,
17+ is_ui_enabled , license_path, set_bundled_plugin_directory, set_license, string:: IntoJson ,
1818} ;
1919use std:: io;
2020use std:: path:: { Path , PathBuf } ;
21+ use std:: sync:: atomic:: AtomicUsize ;
2122use std:: sync:: atomic:: Ordering :: SeqCst ;
22- use std:: sync:: atomic:: { AtomicBool , AtomicUsize } ;
2323use thiserror:: Error ;
2424
25+ use crate :: enterprise:: EnterpriseCheckoutStatus ;
2526use crate :: main_thread:: { MainThreadAction , MainThreadHandler } ;
2627use crate :: progress:: ProgressCallback ;
2728use crate :: rc:: Ref ;
@@ -32,12 +33,9 @@ use std::thread::JoinHandle;
3233use std:: time:: Duration ;
3334
3435static MAIN_THREAD_HANDLE : Mutex < Option < JoinHandle < ( ) > > > = Mutex :: new ( None ) ;
35- /// Prevent two threads from calling init() at the same time
36- static INIT_LOCK : Mutex < ( ) > = Mutex :: new ( ( ) ) ;
37- /// Used to prevent shutting down Binary Ninja if there are other [`Session`]'s.
36+
37+ /// Used to prevent shutting down Binary Ninja if there is another active [`Session`].
3838static 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 ) ;
4139
4240#[ derive( Error , Debug ) ]
4341pub enum InitializationError {
@@ -49,15 +47,15 @@ pub enum InitializationError {
4947 InvalidLicense ,
5048 #[ error( "no license could located, please see `binaryninja::set_license` for details" ) ]
5149 NoLicenseFound ,
52- #[ error( "could not acquire initialization mutex " ) ]
53- InitMutex ,
50+ #[ error( "initialization already managed by ui " ) ]
51+ AlreadyManaged ,
5452}
5553
5654/// Loads plugins, core architecture, platform, etc.
5755///
5856/// ⚠️ Important! Must be called at the beginning of scripts. Plugins do not need to call this. ⚠️
5957///
60- /// You can instead call this through [`Session`].
58+ /// The preferred method for core initialization is [`Session`], use that instead of this where possible .
6159///
6260/// If you need to customize initialization, use [`init_with_opts`] instead.
6361pub fn init ( ) -> Result < ( ) , InitializationError > {
@@ -67,15 +65,12 @@ pub fn init() -> Result<(), InitializationError> {
6765
6866/// Unloads plugins, stops all worker threads, and closes open logs.
6967///
70- /// If the core was initialized using an enterprise license, that will also be freed.
71- ///
72- /// ⚠️ Important! Must be called at the end of scripts. ⚠️
68+ /// This function does _NOT_ release floating licenses; it is expected that you call [`enterprise::release_license`].
7369pub fn shutdown ( ) {
74- match crate :: product ( ) . to_string ( ) . as_str ( ) {
70+ match crate :: product ( ) . as_str ( ) {
7571 "Binary Ninja Enterprise Client" | "Binary Ninja Ultimate" => {
76- if NEED_LICENSE_RELEASE . load ( SeqCst ) {
77- enterprise:: release_license ( )
78- }
72+ // By default, we do not release floating licenses.
73+ enterprise:: release_license ( false )
7974 }
8075 _ => { }
8176 }
@@ -91,9 +86,9 @@ pub fn is_shutdown_requested() -> bool {
9186pub struct InitializationOptions {
9287 /// A license to override with, you can use this to make sure you initialize with a specific license.
9388 pub license : Option < String > ,
94- /// If you need to make sure that you do not check out a license set this to false.
89+ /// If you need to make sure that you do not check out a license, set this to false.
9590 ///
96- /// This is really only useful if you have a headless license but are using an enterprise enabled core.
91+ /// This is really only useful if you have a headless license but are using an enterprise- enabled core.
9792 pub checkout_license : bool ,
9893 /// Whether to register the default main thread handler.
9994 ///
@@ -105,11 +100,11 @@ pub struct InitializationOptions {
105100 pub bundled_plugin_directory : PathBuf ,
106101 /// Whether to initialize user plugins.
107102 ///
108- /// Set this to false if your use might be impacted by a user installed plugin.
103+ /// Set this to false if your use might be impacted by a user- installed plugin.
109104 pub user_plugins : bool ,
110105 /// Whether to initialize repo plugins.
111106 ///
112- /// Set this to false if your use might be impacted by a repo installed plugin.
107+ /// Set this to false if your use might be impacted by a repo- installed plugin.
113108 pub repo_plugins : bool ,
114109}
115110
@@ -129,9 +124,9 @@ impl InitializationOptions {
129124 self
130125 }
131126
132- /// If you need to make sure that you do not check out a license set this to false.
127+ /// If you need to make sure that you do not check out a license, set this to false.
133128 ///
134- /// This is really only useful if you have a headless license but are using an enterprise enabled core.
129+ /// This is really only useful if you have a headless license but are using an enterprise- enabled core.
135130 pub fn with_license_checkout ( mut self , should_checkout : bool ) -> Self {
136131 self . checkout_license = should_checkout;
137132 self
@@ -151,13 +146,13 @@ impl InitializationOptions {
151146 self
152147 }
153148
154- /// Set this to false if your use might be impacted by a user installed plugin.
149+ /// Set this to false if your use might be impacted by a user- installed plugin.
155150 pub fn with_user_plugins ( mut self , should_initialize : bool ) -> Self {
156151 self . user_plugins = should_initialize;
157152 self
158153 }
159154
160- /// Set this to false if your use might be impacted by a repo installed plugin.
155+ /// Set this to false if your use might be impacted by a repo- installed plugin.
161156 pub fn with_repo_plugins ( mut self , should_initialize : bool ) -> Self {
162157 self . repo_plugins = should_initialize;
163158 self
@@ -181,7 +176,11 @@ impl Default for InitializationOptions {
181176
182177/// This initializes the core with the given [`InitializationOptions`].
183178pub fn init_with_opts ( options : InitializationOptions ) -> Result < ( ) , InitializationError > {
184- // If we are the main thread that means there is no main thread, we should register a main thread handler.
179+ if is_ui_enabled ( ) {
180+ return Err ( InitializationError :: AlreadyManaged ) ;
181+ }
182+
183+ // If we are the main thread, that means there is no main thread, we should register a main thread handler.
185184 if options. register_main_thread_handler && is_main_thread ( ) {
186185 let mut main_thread_handle = MAIN_THREAD_HANDLE . lock ( ) . unwrap ( ) ;
187186 if main_thread_handle. is_none ( ) {
@@ -192,7 +191,7 @@ pub fn init_with_opts(options: InitializationOptions) -> Result<(), Initializati
192191 let join_handle = std:: thread:: Builder :: new ( )
193192 . name ( "HeadlessMainThread" . to_string ( ) )
194193 . spawn ( move || {
195- // We must register the main thread within said thread.
194+ // We must register the main thread within the thread.
196195 main_thread. register ( ) ;
197196 while let Ok ( action) = receiver. recv ( ) {
198197 action. execute ( ) ;
@@ -204,16 +203,13 @@ pub fn init_with_opts(options: InitializationOptions) -> Result<(), Initializati
204203 }
205204 }
206205
207- match crate :: product ( ) . as_str ( ) {
208- "Binary Ninja Enterprise Client" | "Binary Ninja Ultimate" => {
209- if options. checkout_license {
210- // We are allowed to check out a license, so do it!
211- if enterprise:: checkout_license ( options. floating_license_duration ) ? {
212- NEED_LICENSE_RELEASE . store ( true , SeqCst ) ;
213- }
214- }
206+ if is_enterprise_product ( ) && options. checkout_license {
207+ // We are allowed to check out a license, so do it!
208+ let checkout_status = enterprise:: checkout_license ( options. floating_license_duration ) ?;
209+ if checkout_status == EnterpriseCheckoutStatus :: AlreadyManaged {
210+ // Should be impossible, but just in case.
211+ return Err ( InitializationError :: AlreadyManaged ) ;
215212 }
216- _ => { }
217213 }
218214
219215 if let Some ( license) = options. license {
@@ -232,7 +228,7 @@ pub fn init_with_opts(options: InitializationOptions) -> Result<(), Initializati
232228 }
233229
234230 if !is_license_validated ( ) {
235- // Unfortunately you must have a valid license to use Binary Ninja.
231+ // Unfortunately, you must have a valid license to use Binary Ninja.
236232 Err ( InitializationError :: InvalidLicense )
237233 } else {
238234 Ok ( ( ) )
@@ -258,65 +254,63 @@ impl MainThreadHandler for HeadlessMainThreadSender {
258254 }
259255}
260256
257+ fn is_enterprise_product ( ) -> bool {
258+ match crate :: product ( ) . as_str ( ) {
259+ "Binary Ninja Enterprise Client" | "Binary Ninja Ultimate" => true ,
260+ _ => false ,
261+ }
262+ }
263+
261264#[ derive( Debug , PartialEq , Eq , Clone , Copy , Hash ) ]
262265pub enum LicenseLocation {
263266 /// The license used when initializing will be the environment variable `BN_LICENSE`.
264267 EnvironmentVariable ,
265268 /// The license used when initializing will be the file in the Binary Ninja user directory.
266269 File ,
267- /// The license is retrieved using keychain credentials for `BN_ENTERPRISE_USERNAME` .
270+ /// The license is retrieved using keychain credentials, this is only available for floating enterprise licenses .
268271 Keychain ,
269272}
270273
271274/// Attempts to identify the license location type, this follows the same order as core initialization.
272275///
273276/// This is useful if you want to know whether the core will use your license. If this returns `None`
274- /// you should look setting the `BN_LICENSE` environment variable, or calling [`set_license`].
277+ /// you should look into setting the `BN_LICENSE` environment variable or calling [`set_license`].
275278pub fn license_location ( ) -> Option < LicenseLocation > {
276279 match std:: env:: var ( "BN_LICENSE" ) {
277280 Ok ( _) => Some ( LicenseLocation :: EnvironmentVariable ) ,
278281 Err ( _) => {
279282 // Check the license_path to see if a file is there.
280283 if license_path ( ) . exists ( ) {
281284 Some ( LicenseLocation :: File )
282- } else {
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
285+ } else if is_enterprise_product ( ) {
286+ // If we can't initialize enterprise, we probably are missing enterprise.server.url
287+ // and our license surely is not valid.
288+ if !enterprise:: is_server_initialized ( ) && !enterprise:: initialize_server ( ) {
289+ return None ;
300290 }
291+ // If Enterprise thinks we are using a floating license, then report it will be in the keychain
292+ enterprise:: is_server_floating_license ( ) . then_some ( LicenseLocation :: Keychain )
293+ } else {
294+ // If we are not using an enterprise license, we can't check the keychain, nowhere else to check.
295+ None
301296 }
302297 }
303298 }
304299}
305300
306301/// 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.
302+ #[ derive( Debug , Default , Clone , PartialEq , Eq , Hash ) ]
307303pub struct Session {
308- index : usize ,
304+ license_duration : Option < Duration > ,
309305}
310306
311307impl Session {
312308 /// Get a registered [`Session`] for use.
313309 ///
314310 /// This is required so that we can keep track of the [`SESSION_COUNT`].
315311 fn registered_session ( ) -> Self {
316- let previous_count = SESSION_COUNT . fetch_add ( 1 , SeqCst ) ;
317- Self {
318- index : previous_count,
319- }
312+ let _previous_count = SESSION_COUNT . fetch_add ( 1 , SeqCst ) ;
313+ Self :: default ( )
320314 }
321315
322316 /// Before calling new you must make sure that the license is retrievable, otherwise the core won't be able to initialize.
@@ -326,19 +320,7 @@ impl Session {
326320 pub fn new ( ) -> Result < Self , InitializationError > {
327321 if license_location ( ) . is_some ( ) {
328322 // We were able to locate a license, continue with initialization.
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 ) ?;
335- let session = Self :: registered_session ( ) ;
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- }
341- Ok ( session)
323+ Self :: new_with_opts ( InitializationOptions :: default ( ) )
342324 } else {
343325 // There was no license that could be automatically retrieved, you must call [Self::new_with_license].
344326 Err ( InitializationError :: NoLicenseFound )
@@ -348,19 +330,10 @@ impl Session {
348330 /// Initialize with options, the same rules apply as [`Session::new`], see [`InitializationOptions::default`] for the regular options passed.
349331 ///
350332 /// This differs from [`Session::new`] in that it does not check to see if there is a license that the core
351- /// can discover by itself, therefor it is expected that you know where your license is when calling this directly.
333+ /// can discover by itself, therefore, it is expected that you know where your license is when calling this directly.
352334 pub fn new_with_opts ( options : InitializationOptions ) -> Result < Self , InitializationError > {
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 ) ?;
358335 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- }
336+ init_with_opts ( options) ?;
364337 Ok ( session)
365338 }
366339
@@ -457,7 +430,7 @@ impl Drop for Session {
457430 fn drop ( & mut self ) {
458431 let previous_count = SESSION_COUNT . fetch_sub ( 1 , SeqCst ) ;
459432 if previous_count == 1 {
460- // We were the last session, therefor we can safely shut down.
433+ // We were the last session, therefore, we can safely shut down.
461434 shutdown ( ) ;
462435 }
463436 }
0 commit comments