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}