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::hash::{Hash, Hasher};
26use std::path::{Path, PathBuf};
27
28use crate::progress::{NoProgressCallback, ProgressCallback};
29use crate::project::file::ProjectFile;
30use std::ptr::NonNull;
31
32#[allow(unused_imports)]
33use crate::binary_view::BinaryViewType;
34
35new_id_type!(SessionId, usize);
36
37pub type SaveOption = BNSaveOption;
38
39/// Settings to alter the behavior of creating snapshots saved within a [`Database`].
40pub struct SaveSettings {
41    pub(crate) handle: *mut BNSaveSettings,
42}
43
44impl SaveSettings {
45    pub fn new() -> Ref<Self> {
46        Self::ref_from_raw(unsafe { BNCreateSaveSettings() })
47    }
48
49    fn ref_from_raw(handle: *mut BNSaveSettings) -> Ref<Self> {
50        unsafe { Ref::new(Self { handle }) }
51    }
52
53    /// Sets the specified `option` to `true` and returns a ref counted `SaveSettings` that can
54    /// continued to be chained.
55    pub fn with_option(&self, option: SaveOption) -> Ref<Self> {
56        self.set_option(option, true);
57        self.to_owned()
58    }
59
60    pub fn set_option(&self, option: SaveOption, value: bool) {
61        unsafe { BNSetSaveSettingsOption(self.handle, option, value) }
62    }
63
64    pub fn option(&self, option: SaveOption) -> bool {
65        unsafe { BNIsSaveSettingsOptionSet(self.handle, option) }
66    }
67
68    /// When saving an automatic snapshot via [`FileMetadata::save_auto_snapshot`] this name will be
69    /// used for the newly written snapshot.
70    pub fn snapshot_name(&self) -> String {
71        unsafe { BnString::into_string(BNGetSaveSettingsName(self.handle)) }
72    }
73
74    pub fn set_snapshot_name(&self, name: &str) {
75        let name = name.to_cstr();
76        unsafe { BNSetSaveSettingsName(self.handle, name.as_ptr()) }
77    }
78}
79
80unsafe impl Send for SaveSettings {}
81unsafe impl Sync for SaveSettings {}
82
83impl ToOwned for SaveSettings {
84    type Owned = Ref<Self>;
85
86    fn to_owned(&self) -> Self::Owned {
87        unsafe { RefCountable::inc_ref(self) }
88    }
89}
90
91unsafe impl RefCountable for SaveSettings {
92    unsafe fn inc_ref(handle: &Self) -> Ref<Self> {
93        Ref::new(Self {
94            handle: BNNewSaveSettingsReference(handle.handle),
95        })
96    }
97
98    unsafe fn dec_ref(handle: &Self) {
99        BNFreeSaveSettings(handle.handle);
100    }
101}
102
103/// File metadata provides information about a file in the context of Binary Ninja. It contains no
104/// analysis information, only information useful for identifying a file, such as the [`FileMetadata::file_path`].
105///
106/// Another responsibility of the [`FileMetadata`] is to own the available [`BinaryView`]s for the
107/// file, such as the "Raw" view and any other views that may be created for the file.
108///
109/// **Important**: Because [`FileMetadata`] holds a strong reference to the [`BinaryView`]s and those
110/// views hold a strong reference to the file metadata, to end the cyclic reference a call to the
111/// [`FileMetadata::close`] is required.
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    ///
476    /// Because the type may not have been registered, and the actual [`BinaryViewType`] is not available,
477    /// we instead return the name of the view type.
478    pub fn view_types(&self) -> Array<BnString> {
479        let mut count = 0;
480        unsafe {
481            let types = BNGetExistingViews(self.handle, &mut count);
482            Array::new(types, count, ())
483        }
484    }
485
486    /// Get the [`ProjectFile`] for the [`FileMetadata`].
487    pub fn project_file(&self) -> Option<Ref<ProjectFile>> {
488        unsafe {
489            let res = NonNull::new(BNGetProjectFile(self.handle))?;
490            Some(ProjectFile::ref_from_raw(res))
491        }
492    }
493
494    /// Create a database for the file and its views at `file_path`.
495    ///
496    /// NOTE: Calling this while analysis is running will flag the next load of the database to
497    /// regenerate the current analysis.
498    pub fn create_database(&self, file_path: impl AsRef<Path>, settings: &SaveSettings) -> bool {
499        self.create_database_with_progress(file_path, settings, NoProgressCallback)
500    }
501
502    /// Create a database for the file and its views at `file_path`, with a progress callback.
503    ///
504    /// NOTE: Calling this while analysis is running will flag the next load of the database to
505    /// regenerate the current analysis.
506    pub fn create_database_with_progress<P: ProgressCallback>(
507        &self,
508        file_path: impl AsRef<Path>,
509        settings: &SaveSettings,
510        mut progress: P,
511    ) -> bool {
512        // Databases are created with the root view (Raw).
513        let raw_view = self.raw_view();
514        let file_path = file_path.as_ref().to_cstr();
515        unsafe {
516            BNCreateDatabaseWithProgress(
517                raw_view.handle,
518                file_path.as_ptr() as *mut _,
519                &mut progress as *mut P as *mut c_void,
520                Some(P::cb_progress_callback),
521                settings.handle,
522            )
523        }
524    }
525
526    /// Save a new snapshot of the current file.
527    ///
528    /// NOTE: Calling this while analysis is running will flag the next load of the database to
529    /// regenerate the current analysis.
530    pub fn save_auto_snapshot(&self) -> bool {
531        // Snapshots are saved with the root view (Raw).
532        let raw_view = self.raw_view();
533        unsafe { BNSaveAutoSnapshot(raw_view.handle, std::ptr::null_mut() as *mut _) }
534    }
535
536    // TODO: Deprecate this function? Does not seem to do anything different than `open_database`.
537    pub fn open_database_for_configuration(&self, file: &Path) -> Result<Ref<BinaryView>, ()> {
538        let file = file.to_cstr();
539        unsafe {
540            let bv =
541                BNOpenDatabaseForConfiguration(self.handle, file.as_ref().as_ptr() as *const _);
542
543            if bv.is_null() {
544                Err(())
545            } else {
546                Ok(BinaryView::ref_from_raw(bv))
547            }
548        }
549    }
550
551    // TODO: How this relates to `BNLoadFilename`?
552    pub fn open_database(&self, file: &Path) -> Result<Ref<BinaryView>, ()> {
553        let file = file.to_cstr();
554        let view = unsafe { BNOpenExistingDatabase(self.handle, file.as_ptr()) };
555
556        if view.is_null() {
557            Err(())
558        } else {
559            Ok(unsafe { BinaryView::ref_from_raw(view) })
560        }
561    }
562
563    // TODO: How this relates to `BNLoadFilename`?
564    pub fn open_database_with_progress<P: ProgressCallback>(
565        &self,
566        file: &Path,
567        mut progress: P,
568    ) -> Result<Ref<BinaryView>, ()> {
569        let file = file.to_cstr();
570
571        let view = unsafe {
572            BNOpenExistingDatabaseWithProgress(
573                self.handle,
574                file.as_ptr(),
575                &mut progress as *mut P as *mut c_void,
576                Some(P::cb_progress_callback),
577            )
578        };
579
580        if view.is_null() {
581            Err(())
582        } else {
583            Ok(unsafe { BinaryView::ref_from_raw(view) })
584        }
585    }
586
587    /// Get the database attached to this file.
588    ///
589    /// Only available if this file is a database, or [`FileMetadata::create_database`] has previously
590    /// been called on this file.
591    pub fn database(&self) -> Option<Ref<Database>> {
592        let result = unsafe { BNGetFileMetadataDatabase(self.handle) };
593        NonNull::new(result).map(|handle| unsafe { Database::ref_from_raw(handle) })
594    }
595}
596
597impl Debug for FileMetadata {
598    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
599        f.debug_struct("FileMetadata")
600            .field("file_path", &self.file_path())
601            .field("display_name", &self.display_name())
602            .field("session_id", &self.session_id())
603            .field("is_modified", &self.is_modified())
604            .field("is_analysis_changed", &self.is_analysis_changed())
605            .field("current_view_type", &self.current_view())
606            .field("current_offset", &self.current_offset())
607            .field("view_types", &self.view_types().to_vec())
608            .finish()
609    }
610}
611
612impl Display for FileMetadata {
613    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
614        f.write_str(&self.display_name())
615    }
616}
617
618impl PartialEq for FileMetadata {
619    fn eq(&self, other: &Self) -> bool {
620        self.session_id() == other.session_id()
621    }
622}
623
624impl Eq for FileMetadata {}
625
626impl Hash for FileMetadata {
627    fn hash<H: Hasher>(&self, state: &mut H) {
628        self.session_id().hash(state);
629    }
630}
631
632unsafe impl Send for FileMetadata {}
633unsafe impl Sync for FileMetadata {}
634
635impl ToOwned for FileMetadata {
636    type Owned = Ref<Self>;
637
638    fn to_owned(&self) -> Self::Owned {
639        unsafe { RefCountable::inc_ref(self) }
640    }
641}
642
643unsafe impl RefCountable for FileMetadata {
644    unsafe fn inc_ref(handle: &Self) -> Ref<Self> {
645        Ref::new(Self {
646            handle: BNNewFileReference(handle.handle),
647        })
648    }
649
650    unsafe fn dec_ref(handle: &Self) {
651        BNFreeFileMetadata(handle.handle);
652    }
653}