binaryninja/file_metadata.rs
1// Copyright 2021-2026 Vector 35 Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! The [`FileMetadata`] struct provides information about a file and owns its available [`BinaryView`]s.
16
17use crate::binary_view::BinaryView;
18use crate::database::Database;
19use crate::rc::*;
20use crate::string::*;
21use binaryninjacore_sys::*;
22use binaryninjacore_sys::{BNCreateDatabaseWithProgress, BNOpenExistingDatabaseWithProgress};
23use std::ffi::c_void;
24use std::fmt::{Debug, Display, Formatter};
25use std::path::{Path, PathBuf};
26
27use crate::progress::{NoProgressCallback, ProgressCallback};
28use crate::project::file::ProjectFile;
29use std::ptr::NonNull;
30
31#[allow(unused_imports)]
32use crate::custom_binary_view::BinaryViewType;
33
34new_id_type!(SessionId, usize);
35
36pub type SaveOption = BNSaveOption;
37
38/// Settings to alter the behavior of creating snapshots saved within a [`Database`].
39pub struct SaveSettings {
40 pub(crate) handle: *mut BNSaveSettings,
41}
42
43impl SaveSettings {
44 pub fn new() -> Ref<Self> {
45 Self::ref_from_raw(unsafe { BNCreateSaveSettings() })
46 }
47
48 fn ref_from_raw(handle: *mut BNSaveSettings) -> Ref<Self> {
49 unsafe { Ref::new(Self { handle }) }
50 }
51
52 /// Sets the specified `option` to `true` and returns a ref counted `SaveSettings` that can
53 /// continued to be chained.
54 pub fn with_option(&self, option: SaveOption) -> Ref<Self> {
55 self.set_option(option, true);
56 self.to_owned()
57 }
58
59 pub fn set_option(&self, option: SaveOption, value: bool) {
60 unsafe { BNSetSaveSettingsOption(self.handle, option, value) }
61 }
62
63 pub fn option(&self, option: SaveOption) -> bool {
64 unsafe { BNIsSaveSettingsOptionSet(self.handle, option) }
65 }
66
67 /// When saving an automatic snapshot via [`FileMetadata::save_auto_snapshot`] this name will be
68 /// used for the newly written snapshot.
69 pub fn snapshot_name(&self) -> String {
70 unsafe { BnString::into_string(BNGetSaveSettingsName(self.handle)) }
71 }
72
73 pub fn set_snapshot_name(&self, name: &str) {
74 let name = name.to_cstr();
75 unsafe { BNSetSaveSettingsName(self.handle, name.as_ptr()) }
76 }
77}
78
79unsafe impl Send for SaveSettings {}
80unsafe impl Sync for SaveSettings {}
81
82impl ToOwned for SaveSettings {
83 type Owned = Ref<Self>;
84
85 fn to_owned(&self) -> Self::Owned {
86 unsafe { RefCountable::inc_ref(self) }
87 }
88}
89
90unsafe impl RefCountable for SaveSettings {
91 unsafe fn inc_ref(handle: &Self) -> Ref<Self> {
92 Ref::new(Self {
93 handle: BNNewSaveSettingsReference(handle.handle),
94 })
95 }
96
97 unsafe fn dec_ref(handle: &Self) {
98 BNFreeSaveSettings(handle.handle);
99 }
100}
101
102/// File metadata provides information about a file in the context of Binary Ninja. It contains no
103/// analysis information, only information useful for identifying a file, such as the [`FileMetadata::file_path`].
104///
105/// Another responsibility of the [`FileMetadata`] is to own the available [`BinaryView`]s for the
106/// file, such as the "Raw" view and any other views that may be created for the file.
107///
108/// **Important**: Because [`FileMetadata`] holds a strong reference to the [`BinaryView`]s and those
109/// views hold a strong reference to the file metadata, to end the cyclic reference a call to the
110/// [`FileMetadata::close`] is required.
111#[derive(PartialEq, Eq, Hash)]
112pub struct FileMetadata {
113 pub(crate) handle: *mut BNFileMetadata,
114}
115
116impl FileMetadata {
117 pub(crate) fn from_raw(handle: *mut BNFileMetadata) -> Self {
118 Self { handle }
119 }
120
121 pub(crate) fn ref_from_raw(handle: *mut BNFileMetadata) -> Ref<Self> {
122 unsafe { Ref::new(Self { handle }) }
123 }
124
125 /// Create an empty [`FileMetadata`] with no associated file path.
126 ///
127 /// Unless you are creating an ephemeral file with no backing, prefer [`FileMetadata::with_file_path`].
128 pub fn new() -> Ref<Self> {
129 Self::ref_from_raw(unsafe { BNCreateFileMetadata() })
130 }
131
132 /// Build a [`FileMetadata`] with the given `path`.
133 pub fn with_file_path(path: &Path) -> Ref<Self> {
134 let ret = FileMetadata::new();
135 ret.set_file_path(path);
136 ret
137 }
138
139 /// Closes the [`FileMetadata`] allowing any [`BinaryView`] parented to it to be freed.
140 pub fn close(&self) {
141 unsafe {
142 BNCloseFile(self.handle);
143 }
144 }
145
146 /// An id unique to this [`FileMetadata`], mostly used for associating logs with a specific file.
147 pub fn session_id(&self) -> SessionId {
148 let raw = unsafe { BNFileMetadataGetSessionId(self.handle) };
149 SessionId(raw)
150 }
151
152 /// The path to the [`FileMetadata`] on disk.
153 ///
154 /// This will not point to the original file on disk, in the event that the file was saved
155 /// as a BNDB. When a BNDB is opened, the FileMetadata will contain the file path to the database.
156 ///
157 /// If you need the original binary file path, use [`FileMetadata::original_file_path`] instead.
158 ///
159 /// If you just want a name to present to the user, use [`FileMetadata::display_name`].
160 pub fn file_path(&self) -> PathBuf {
161 unsafe {
162 let raw = BNGetFilename(self.handle);
163 PathBuf::from(BnString::into_string(raw))
164 }
165 }
166
167 // TODO: To prevent issues we will not allow users to set the file path as it really should be
168 // TODO: derived at construction and not modified later.
169 /// Set the files path on disk.
170 ///
171 /// This should always be a valid path.
172 pub(crate) fn set_file_path(&self, name: &Path) {
173 let name = name.to_cstr();
174 unsafe {
175 BNSetFilename(self.handle, name.as_ptr());
176 }
177 }
178
179 /// The display name of the file. Useful for presenting to the user. Can differ from the original
180 /// name of the file and can be overridden with [`FileMetadata::set_display_name`].
181 pub fn display_name(&self) -> String {
182 let raw_name = unsafe {
183 let raw = BNGetDisplayName(self.handle);
184 BnString::into_string(raw)
185 };
186 // Sometimes this display name may return a full path, which is not the intended purpose.
187 raw_name
188 .split('/')
189 .next_back()
190 .unwrap_or(&raw_name)
191 .to_string()
192 }
193
194 /// Set the display name of the file.
195 ///
196 /// This can be anything and will not be used for any purpose other than presentation.
197 pub fn set_display_name(&self, name: &str) {
198 let name = name.to_cstr();
199 unsafe {
200 BNSetDisplayName(self.handle, name.as_ptr());
201 }
202 }
203
204 /// The path to the original file on disk, if any.
205 ///
206 /// It may not be present if the BNDB was saved without it or cleared via [`FileMetadata::clear_original_file_path`].
207 ///
208 /// If this [`FileMetadata`] is a database within a project, it may not have a "consumable" original
209 /// file path. Instead, this might return the path to the on disk file path of the project file that
210 /// this database was created from, for projects you should query through [`FileMetadata::project_file`].
211 ///
212 /// Only prefer this over [`FileMetadata::file_path`] if you require the original binary location.
213 pub fn original_file_path(&self) -> Option<PathBuf> {
214 let raw_name = unsafe {
215 let raw = BNGetOriginalFilename(self.handle);
216 PathBuf::from(BnString::into_string(raw))
217 };
218 // If the original file path is empty, or the original file path is pointing to the same file
219 // as the database itself, we know the original file path does not exist.
220 if raw_name.as_os_str().is_empty()
221 || self.is_database_backed() && raw_name == self.file_path()
222 {
223 None
224 } else {
225 Some(raw_name)
226 }
227 }
228
229 /// Set the original file path inside the database. Useful if it has since been cleared from the
230 /// database, or you have moved the original file.
231 pub fn set_original_file_path(&self, path: &Path) {
232 let name = path.to_cstr();
233 unsafe {
234 BNSetOriginalFilename(self.handle, name.as_ptr());
235 }
236 }
237
238 /// Clear the original file path inside the database. This is useful since the original file path
239 /// may be sensitive information you wish to not share with others.
240 pub fn clear_original_file_path(&self) {
241 unsafe {
242 BNSetOriginalFilename(self.handle, std::ptr::null());
243 }
244 }
245
246 /// The non-filesystem path that describes how this file was derived from the container
247 /// transform system, detailing the sequence of transform steps and selection names.
248 ///
249 /// NOTE: Returns `None` if this [`FileMetadata`] was not processed by the transform system and
250 /// does not differ from that of the "physical" file path reported by [`FileMetadata::file_path`].
251 pub fn virtual_path(&self) -> Option<String> {
252 unsafe {
253 let raw = BNGetVirtualPath(self.handle);
254 let path = BnString::into_string(raw);
255 // For whatever reason the core may report there being a virtual path as the file path.
256 // In the case where that occurs, we wish not to report there being one to the user.
257 match path.is_empty() || path == self.file_path() {
258 true => None,
259 false => Some(path),
260 }
261 }
262 }
263
264 /// Sets the non-filesystem path that describes how this file was derived from the container
265 /// transform system.
266 pub fn set_virtual_path(&self, path: &str) {
267 let path = path.to_cstr();
268 unsafe {
269 BNSetVirtualPath(self.handle, path.as_ptr());
270 }
271 }
272
273 /// Whether the file is currently flagged as modified.
274 ///
275 /// When this returns `true`, the UI will prompt to save the database on close, as well as display
276 /// a dot in the files tab.
277 pub fn is_modified(&self) -> bool {
278 unsafe { BNIsFileModified(self.handle) }
279 }
280
281 /// Marks the file as modified such that we can prompt to save the database on close.
282 pub fn mark_modified(&self) {
283 unsafe {
284 BNMarkFileModified(self.handle);
285 }
286 }
287
288 /// Marks the file as saved such that [`FileMetadata::is_modified`] and [`FileMetadata::is_analysis_changed`]
289 /// will return `false` and the undo buffer associated with this [`FileMetadata`] will be updated.
290 pub fn mark_saved(&self) {
291 unsafe {
292 BNMarkFileSaved(self.handle);
293 }
294 }
295
296 pub fn is_analysis_changed(&self) -> bool {
297 unsafe { BNIsAnalysisChanged(self.handle) }
298 }
299
300 /// Checks to see if the database exists for the file.
301 pub fn is_database_backed(&self) -> bool {
302 // TODO: This seems to be a useless function. Replace with a call to file.database().is_some()?
303 self.is_database_backed_for_view_type("")
304 }
305
306 /// Checks to see if the file metadata has a [`Database`], and then checks to see if the `view_type`
307 /// is available.
308 ///
309 /// NOTE: Passing an empty string will simply check if the database exists.
310 pub fn is_database_backed_for_view_type(&self, view_type: &str) -> bool {
311 let view_type = view_type.to_cstr();
312 // TODO: This seems to be a useless function. Replace with a call to file.database().is_some()?
313 unsafe { BNIsBackedByDatabase(self.handle, view_type.as_ref().as_ptr() as *const _) }
314 }
315
316 /// Runs a failable function where the failure state will revert any undo actions that occurred
317 /// during the time of the function's execution.
318 ///
319 /// NOTE: This will commit or undo any actions that occurred on **any** thread as this state is not thread local.
320 ///
321 /// NOTE: This is **NOT** thread safe, if you are holding any locks that might be held by both the main thread
322 /// and the thread executing this function, you can deadlock. You should also never call this function
323 /// on multiple threads at a time. See the following issues:
324 /// - <https://github.com/Vector35/binaryninja-api/issues/6289>
325 /// - <https://github.com/Vector35/binaryninja-api/issues/6325>
326 pub fn run_undoable_transaction<F: FnOnce() -> Result<T, E>, T, E>(
327 &self,
328 func: F,
329 ) -> Result<T, E> {
330 let undo = self.begin_undo_actions(false);
331 let result = func();
332 match result {
333 Ok(t) => {
334 self.commit_undo_actions(&undo);
335 Ok(t)
336 }
337 Err(e) => {
338 self.revert_undo_actions(&undo);
339 Err(e)
340 }
341 }
342 }
343
344 /// Creates a new undo entry, any undo actions after this will be added to this entry.
345 ///
346 /// NOTE: This is **NOT** thread safe, if you are holding any locks that might be held by both the main thread
347 /// and the thread executing this function, you can deadlock. You should also never call this function
348 /// on multiple threads at a time. See the following issues:
349 /// - <https://github.com/Vector35/binaryninja-api/issues/6289>
350 /// - <https://github.com/Vector35/binaryninja-api/issues/6325>
351 pub fn begin_undo_actions(&self, anonymous_allowed: bool) -> String {
352 unsafe { BnString::into_string(BNBeginUndoActions(self.handle, anonymous_allowed)) }
353 }
354
355 /// Commits the undo entry with the id to the undo buffer.
356 ///
357 /// NOTE: This is **NOT** thread safe, if you are holding any locks that might be held by both the main thread
358 /// and the thread executing this function, you can deadlock. You should also never call this function
359 /// on multiple threads at a time. See the following issues:
360 /// - <https://github.com/Vector35/binaryninja-api/issues/6289>
361 /// - <https://github.com/Vector35/binaryninja-api/issues/6325>
362 pub fn commit_undo_actions(&self, id: &str) {
363 let id = id.to_cstr();
364 unsafe {
365 BNCommitUndoActions(self.handle, id.as_ref().as_ptr() as *const _);
366 }
367 }
368
369 /// Reverts the undo actions committed in the undo entry.
370 ///
371 /// NOTE: This is **NOT** thread safe, if you are holding any locks that might be held by both the main thread
372 /// and the thread executing this function, you can deadlock. You should also never call this function
373 /// on multiple threads at a time. See the following issues:
374 /// - <https://github.com/Vector35/binaryninja-api/issues/6289>
375 /// - <https://github.com/Vector35/binaryninja-api/issues/6325>
376 pub fn revert_undo_actions(&self, id: &str) {
377 let id = id.to_cstr();
378 unsafe {
379 BNRevertUndoActions(self.handle, id.as_ref().as_ptr() as *const _);
380 }
381 }
382
383 /// Forgets the undo actions committed in the undo entry.
384 ///
385 /// NOTE: This is **NOT** thread safe, if you are holding any locks that might be held by both the main thread
386 /// and the thread executing this function, you can deadlock. You should also never call this function
387 /// on multiple threads at a time. See the following issues:
388 /// - <https://github.com/Vector35/binaryninja-api/issues/6289>
389 /// - <https://github.com/Vector35/binaryninja-api/issues/6325>
390 pub fn forget_undo_actions(&self, id: &str) {
391 let id = id.to_cstr();
392 unsafe {
393 BNForgetUndoActions(self.handle, id.as_ref().as_ptr() as *const _);
394 }
395 }
396
397 pub fn undo(&self) {
398 unsafe {
399 BNUndo(self.handle);
400 }
401 }
402
403 pub fn redo(&self) {
404 unsafe {
405 BNRedo(self.handle);
406 }
407 }
408
409 /// Retrieve the raw view for the file, this should always be present.
410 ///
411 /// The "Raw" view is a special [`BinaryView`] that holds data required for updating and creating
412 /// [`Database`]s such as the view and load settings.
413 pub fn raw_view(&self) -> Ref<BinaryView> {
414 self.view_of_type("Raw")
415 .expect("Raw view should always be present")
416 }
417
418 /// The current view for the file.
419 ///
420 /// For example, opening a PE file and navigating to the linear view will return "Linear:PE".
421 pub fn current_view(&self) -> String {
422 unsafe { BnString::into_string(BNGetCurrentView(self.handle)) }
423 }
424
425 /// The current offset navigated to within the [`FileMetadata::current_view`].
426 pub fn current_offset(&self) -> u64 {
427 unsafe { BNGetCurrentOffset(self.handle) }
428 }
429
430 /// Navigate to an offset for a specific view.
431 ///
432 /// # Example
433 ///
434 /// ```no_run
435 /// use binaryninja::file_metadata::FileMetadata;
436 /// # let file: FileMetadata = unimplemented!();
437 /// file.navigate_to("Linear:Raw", 0x0).expect("Linear:Raw should always be present");
438 /// ```
439 pub fn navigate_to(&self, view: &str, offset: u64) -> Result<(), ()> {
440 let view = view.to_cstr();
441
442 unsafe {
443 if BNNavigate(self.handle, view.as_ref().as_ptr() as *const _, offset) {
444 Ok(())
445 } else {
446 Err(())
447 }
448 }
449 }
450
451 /// Get the [`BinaryView`] for the view type.
452 ///
453 /// # Example
454 ///
455 /// ```no_run
456 /// use binaryninja::file_metadata::FileMetadata;
457 /// # let file: FileMetadata = unimplemented!();
458 /// file.view_of_type("Raw").expect("Raw type should always be present");
459 /// ```
460 pub fn view_of_type(&self, view: &str) -> Option<Ref<BinaryView>> {
461 let view = view.to_cstr();
462
463 unsafe {
464 let raw_view_ptr = BNGetFileViewOfType(self.handle, view.as_ref().as_ptr() as *const _);
465 match raw_view_ptr.is_null() {
466 false => Some(BinaryView::ref_from_raw(raw_view_ptr)),
467 true => None,
468 }
469 }
470 }
471
472 /// The [`BinaryViewType`]s associated with this file.
473 ///
474 /// For example, opening a PE binary will have the following: "Raw", "PE".
475 pub fn view_types(&self) -> Array<BnString> {
476 let mut count = 0;
477 unsafe {
478 let types = BNGetExistingViews(self.handle, &mut count);
479 Array::new(types, count, ())
480 }
481 }
482
483 /// Get the [`ProjectFile`] for the [`FileMetadata`].
484 pub fn project_file(&self) -> Option<Ref<ProjectFile>> {
485 unsafe {
486 let res = NonNull::new(BNGetProjectFile(self.handle))?;
487 Some(ProjectFile::ref_from_raw(res))
488 }
489 }
490
491 /// Create a database for the file and its views at `file_path`.
492 ///
493 /// NOTE: Calling this while analysis is running will flag the next load of the database to
494 /// regenerate the current analysis.
495 pub fn create_database(&self, file_path: impl AsRef<Path>, settings: &SaveSettings) -> bool {
496 self.create_database_with_progress(file_path, settings, NoProgressCallback)
497 }
498
499 /// Create a database for the file and its views at `file_path`, with a progress callback.
500 ///
501 /// NOTE: Calling this while analysis is running will flag the next load of the database to
502 /// regenerate the current analysis.
503 pub fn create_database_with_progress<P: ProgressCallback>(
504 &self,
505 file_path: impl AsRef<Path>,
506 settings: &SaveSettings,
507 mut progress: P,
508 ) -> bool {
509 // Databases are created with the root view (Raw).
510 let raw_view = self.raw_view();
511 let file_path = file_path.as_ref().to_cstr();
512 unsafe {
513 BNCreateDatabaseWithProgress(
514 raw_view.handle,
515 file_path.as_ptr() as *mut _,
516 &mut progress as *mut P as *mut c_void,
517 Some(P::cb_progress_callback),
518 settings.handle,
519 )
520 }
521 }
522
523 /// Save a new snapshot of the current file.
524 ///
525 /// NOTE: Calling this while analysis is running will flag the next load of the database to
526 /// regenerate the current analysis.
527 pub fn save_auto_snapshot(&self) -> bool {
528 // Snapshots are saved with the root view (Raw).
529 let raw_view = self.raw_view();
530 unsafe { BNSaveAutoSnapshot(raw_view.handle, std::ptr::null_mut() as *mut _) }
531 }
532
533 // TODO: Deprecate this function? Does not seem to do anything different than `open_database`.
534 pub fn open_database_for_configuration(&self, file: &Path) -> Result<Ref<BinaryView>, ()> {
535 let file = file.to_cstr();
536 unsafe {
537 let bv =
538 BNOpenDatabaseForConfiguration(self.handle, file.as_ref().as_ptr() as *const _);
539
540 if bv.is_null() {
541 Err(())
542 } else {
543 Ok(BinaryView::ref_from_raw(bv))
544 }
545 }
546 }
547
548 // TODO: How this relates to `BNLoadFilename`?
549 pub fn open_database(&self, file: &Path) -> Result<Ref<BinaryView>, ()> {
550 let file = file.to_cstr();
551 let view = unsafe { BNOpenExistingDatabase(self.handle, file.as_ptr()) };
552
553 if view.is_null() {
554 Err(())
555 } else {
556 Ok(unsafe { BinaryView::ref_from_raw(view) })
557 }
558 }
559
560 // TODO: How this relates to `BNLoadFilename`?
561 pub fn open_database_with_progress<P: ProgressCallback>(
562 &self,
563 file: &Path,
564 mut progress: P,
565 ) -> Result<Ref<BinaryView>, ()> {
566 let file = file.to_cstr();
567
568 let view = unsafe {
569 BNOpenExistingDatabaseWithProgress(
570 self.handle,
571 file.as_ptr(),
572 &mut progress as *mut P as *mut c_void,
573 Some(P::cb_progress_callback),
574 )
575 };
576
577 if view.is_null() {
578 Err(())
579 } else {
580 Ok(unsafe { BinaryView::ref_from_raw(view) })
581 }
582 }
583
584 /// Get the database attached to this file.
585 ///
586 /// Only available if this file is a database, or has called [`FileMetadata::create_database`].
587 pub fn database(&self) -> Option<Ref<Database>> {
588 let result = unsafe { BNGetFileMetadataDatabase(self.handle) };
589 NonNull::new(result).map(|handle| unsafe { Database::ref_from_raw(handle) })
590 }
591}
592
593impl Debug for FileMetadata {
594 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
595 f.debug_struct("FileMetadata")
596 .field("file_path", &self.file_path())
597 .field("display_name", &self.display_name())
598 .field("session_id", &self.session_id())
599 .field("is_modified", &self.is_modified())
600 .field("is_analysis_changed", &self.is_analysis_changed())
601 .field("current_view_type", &self.current_view())
602 .field("current_offset", &self.current_offset())
603 .field("view_types", &self.view_types().to_vec())
604 .finish()
605 }
606}
607
608impl Display for FileMetadata {
609 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
610 f.write_str(&self.display_name())
611 }
612}
613
614unsafe impl Send for FileMetadata {}
615unsafe impl Sync for FileMetadata {}
616
617impl ToOwned for FileMetadata {
618 type Owned = Ref<Self>;
619
620 fn to_owned(&self) -> Self::Owned {
621 unsafe { RefCountable::inc_ref(self) }
622 }
623}
624
625unsafe impl RefCountable for FileMetadata {
626 unsafe fn inc_ref(handle: &Self) -> Ref<Self> {
627 Ref::new(Self {
628 handle: BNNewFileReference(handle.handle),
629 })
630 }
631
632 unsafe fn dec_ref(handle: &Self) {
633 BNFreeFileMetadata(handle.handle);
634 }
635}