diff options
| author | Mason Reed <mason@vector35.com> | 2026-02-11 17:56:26 -0800 |
|---|---|---|
| committer | Mason Reed <35282038+emesare@users.noreply.github.com> | 2026-02-23 00:09:44 -0800 |
| commit | c1dbea197a6ba7e3d008e01c8169f5c3702151ea (patch) | |
| tree | c4dc5e203e7c04bd7ba1b105bd065073f05774a5 | |
| parent | d61826e8fbe3580c762f7f317f69201bce3710ad (diff) | |
[Rust] Refactor `FileMetadata` file information
- Rename and retype `FileMetadata::filename` and make the assignment required to happen at time of construction.
- Add `FileMetadata::display_name` which is only to be used for presentation purposes.
- Add `FileMetadata::virtual_path` for containers.
- Rename `FileMetadata::modified` to `FileMetadata::is_modified` to be more consistent across codebase.
- Add some missing documentation.
- Add `BinaryView::from_metadata` with accompanying documentation.
| -rw-r--r-- | plugins/dwarf/dwarf_import/src/helpers.rs | 22 | ||||
| -rw-r--r-- | plugins/idb_import/src/lib.rs | 11 | ||||
| -rw-r--r-- | plugins/pdb-ng/src/lib.rs | 2 | ||||
| -rw-r--r-- | plugins/warp/src/cache.rs | 2 | ||||
| -rw-r--r-- | plugins/warp/src/plugin/create.rs | 6 | ||||
| -rw-r--r-- | rust/examples/decompile.rs | 2 | ||||
| -rw-r--r-- | rust/examples/disassemble.rs | 2 | ||||
| -rw-r--r-- | rust/examples/high_level_il.rs | 2 | ||||
| -rw-r--r-- | rust/examples/medium_level_il.rs | 2 | ||||
| -rw-r--r-- | rust/src/binary_view.rs | 19 | ||||
| -rw-r--r-- | rust/src/file_metadata.rs | 128 |
11 files changed, 158 insertions, 40 deletions
diff --git a/plugins/dwarf/dwarf_import/src/helpers.rs b/plugins/dwarf/dwarf_import/src/helpers.rs index c7855b25..541fdc42 100644 --- a/plugins/dwarf/dwarf_import/src/helpers.rs +++ b/plugins/dwarf/dwarf_import/src/helpers.rs @@ -12,8 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::ffi::OsStr; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::{str::FromStr, sync::mpsc}; use crate::{DebugInfoBuilderContext, ReaderType}; @@ -590,10 +589,8 @@ pub(crate) fn find_sibling_debug_file(view: &BinaryView) -> Option<String> { return None; } - let full_file_path = view.file().filename().to_string(); - - let debug_file = PathBuf::from(format!("{}.debug", full_file_path)); - let dsym_folder = PathBuf::from(format!("{}.dSYM", full_file_path)); + let debug_file = view.file().file_path().with_extension("debug"); + let dsym_folder = view.file().file_path().with_extension("dSYM"); // Find sibling debug file if debug_file.exists() && debug_file.is_file() { @@ -624,13 +621,12 @@ pub(crate) fn find_sibling_debug_file(view: &BinaryView) -> Option<String> { // Look for dSYM // TODO: look for dSYM in project if dsym_folder.exists() && dsym_folder.is_dir() { - let filename = Path::new(&full_file_path) - .file_name() - .unwrap_or(OsStr::new("")); - - let dsym_file = dsym_folder.join("Contents/Resources/DWARF/").join(filename); // TODO: should this just pull any file out? Can there be multiple files? - if dsym_file.exists() { - return Some(dsym_file.to_string_lossy().to_string()); + if let Some(filename) = view.file().file_path().file_name() { + // TODO: should this just pull any file out? Can there be multiple files? + let dsym_file = dsym_folder.join("Contents/Resources/DWARF/").join(filename); + if dsym_file.exists() { + return Some(dsym_file.to_string_lossy().to_string()); + } } } diff --git a/plugins/idb_import/src/lib.rs b/plugins/idb_import/src/lib.rs index f285f554..0137f198 100644 --- a/plugins/idb_import/src/lib.rs +++ b/plugins/idb_import/src/lib.rs @@ -27,8 +27,10 @@ impl CustomDebugInfoParser for IDBDebugInfoParser { project_file.name().as_str().ends_with(".i64") || project_file.name().as_str().ends_with(".idb") } else { - view.file().filename().as_str().ends_with(".i64") - || view.file().filename().as_str().ends_with(".idb") + view.file() + .file_path() + .extension() + .map_or(false, |ext| ext == "i64" || ext == "idb") } } @@ -55,7 +57,10 @@ impl CustomDebugInfoParser for TILDebugInfoParser { if let Some(project_file) = view.file().project_file() { project_file.name().as_str().ends_with(".til") } else { - view.file().filename().as_str().ends_with(".til") + view.file() + .file_path() + .extension() + .map_or(false, |ext| ext == "til") } } diff --git a/plugins/pdb-ng/src/lib.rs b/plugins/pdb-ng/src/lib.rs index 86ae1cdd..61ff54b2 100644 --- a/plugins/pdb-ng/src/lib.rs +++ b/plugins/pdb-ng/src/lib.rs @@ -617,7 +617,7 @@ impl CustomDebugInfoParser for PDBParser { } // Try in the same directory as the file - let mut potential_path = PathBuf::from(view.file().filename().to_string()); + let mut potential_path = view.file().file_path(); potential_path.pop(); potential_path.push(&info.file_name); if potential_path.exists() { diff --git a/plugins/warp/src/cache.rs b/plugins/warp/src/cache.rs index 41aab423..3df2dbf8 100644 --- a/plugins/warp/src/cache.rs +++ b/plugins/warp/src/cache.rs @@ -79,6 +79,6 @@ pub struct CacheDestructor; impl ObjectDestructor for CacheDestructor { fn destruct_view(&self, view: &BinaryView) { clear_type_ref_cache(view); - tracing::debug!("Removed WARP caches for {:?}", view.file().filename()); + tracing::debug!("Removed WARP caches for {}", view.file()); } } diff --git a/plugins/warp/src/plugin/create.rs b/plugins/warp/src/plugin/create.rs index 0c6a2fd5..2a748819 100644 --- a/plugins/warp/src/plugin/create.rs +++ b/plugins/warp/src/plugin/create.rs @@ -23,11 +23,7 @@ impl SaveFileField { let default_name = match file.project_file() { None => { // Not in a project, use the file name directly. - file.filename() - .split('/') - .last() - .unwrap_or("file") - .to_string() + file.display_name() } Some(project_file) => project_file.name(), }; diff --git a/rust/examples/decompile.rs b/rust/examples/decompile.rs index 6ba9cbf4..155de81c 100644 --- a/rust/examples/decompile.rs +++ b/rust/examples/decompile.rs @@ -42,7 +42,7 @@ pub fn main() { .load(&filename) .expect("Couldn't open file!"); - tracing::info!("Filename: `{}`", bv.file().filename()); + tracing::info!("File: `{}`", bv.file()); tracing::info!("File size: `{:#x}`", bv.len()); tracing::info!("Function count: {}", bv.functions().len()); diff --git a/rust/examples/disassemble.rs b/rust/examples/disassemble.rs index ce3ff765..df33aade 100644 --- a/rust/examples/disassemble.rs +++ b/rust/examples/disassemble.rs @@ -39,7 +39,7 @@ pub fn main() { .load(&filename) .expect("Couldn't open file!"); - tracing::info!("Filename: `{}`", bv.file().filename()); + tracing::info!("File: `{}`", bv.file()); tracing::info!("File size: `{:#x}`", bv.len()); tracing::info!("Function count: {}", bv.functions().len()); diff --git a/rust/examples/high_level_il.rs b/rust/examples/high_level_il.rs index a099437a..57b735e9 100644 --- a/rust/examples/high_level_il.rs +++ b/rust/examples/high_level_il.rs @@ -14,7 +14,7 @@ fn main() { .load("/bin/cat") .expect("Couldn't open `/bin/cat`"); - tracing::info!("Filename: `{}`", bv.file().filename()); + tracing::info!("File: `{}`", bv.file()); tracing::info!("File size: `{:#x}`", bv.len()); tracing::info!("Function count: {}", bv.functions().len()); diff --git a/rust/examples/medium_level_il.rs b/rust/examples/medium_level_il.rs index c9412d08..e2c0edee 100644 --- a/rust/examples/medium_level_il.rs +++ b/rust/examples/medium_level_il.rs @@ -14,7 +14,7 @@ fn main() { .load("/bin/cat") .expect("Couldn't open `/bin/cat`"); - tracing::info!("Filename: `{}`", bv.file().filename()); + tracing::info!("File: `{}`", bv.file()); tracing::info!("File size: `{:#x}`", bv.len()); tracing::info!("Function count: {}", bv.functions().len()); diff --git a/rust/src/binary_view.rs b/rust/src/binary_view.rs index 478b8265..4e35dc8c 100644 --- a/rust/src/binary_view.rs +++ b/rust/src/binary_view.rs @@ -2476,8 +2476,14 @@ impl BinaryView { Ref::new(Self { handle }) } - pub fn from_path(meta: &mut FileMetadata, file_path: impl AsRef<Path>) -> Result<Ref<Self>> { - let file = file_path.as_ref().to_cstr(); + /// Construct the raw binary view from the given metadata. Before calling this make sure you have + /// a valid file path set for the [`FileMetadata`]. It is required that the [`FileMetadata::file_path`] + /// exist on the local filesystem. + pub fn from_metadata(meta: &FileMetadata) -> Result<Ref<Self>> { + if !meta.file_path().exists() { + return Err(()); + } + let file = meta.file_path().to_cstr(); let handle = unsafe { BNCreateBinaryDataViewFromFilename(meta.handle, file.as_ptr() as *mut _) }; @@ -2488,6 +2494,15 @@ impl BinaryView { unsafe { Ok(Ref::new(Self { handle })) } } + /// Construct the raw binary view from the given `file_path` and metadata. + /// + /// This will implicitly set the metadata file path and then construct the view. If the metadata + /// already has the desired file path, use [`BinaryView::from_metadata`] instead. + pub fn from_path(meta: &FileMetadata, file_path: impl AsRef<Path>) -> Result<Ref<Self>> { + meta.set_file_path(file_path.as_ref()); + Self::from_metadata(meta) + } + pub fn from_accessor<A: Accessor>( meta: &FileMetadata, file: &mut FileAccessor<A>, diff --git a/rust/src/file_metadata.rs b/rust/src/file_metadata.rs index 27ceb4af..f6427750 100644 --- a/rust/src/file_metadata.rs +++ b/rust/src/file_metadata.rs @@ -20,7 +20,7 @@ use binaryninjacore_sys::*; use binaryninjacore_sys::{BNCreateDatabaseWithProgress, BNOpenExistingDatabaseWithProgress}; use std::ffi::c_void; use std::fmt::{Debug, Display, Formatter}; -use std::path::Path; +use std::path::{Path, PathBuf}; use crate::progress::ProgressCallback; use crate::project::file::ProjectFile; @@ -46,12 +46,15 @@ impl FileMetadata { Self::ref_from_raw(unsafe { BNCreateFileMetadata() }) } - pub fn with_filename(name: &str) -> Ref<Self> { + /// Build a [`FileMetadata`] with the given `path`, this is uncommon as you are likely to want to + /// open a [`BinaryView`] + pub fn with_file_path(path: &Path) -> Ref<Self> { let ret = FileMetadata::new(); - ret.set_filename(name); + ret.set_file_path(path); ret } + /// Closes the [`FileMetadata`] allowing any [`BinaryView`] parented to it to be freed. pub fn close(&self) { unsafe { BNCloseFile(self.handle); @@ -63,22 +66,124 @@ impl FileMetadata { SessionId(raw) } - pub fn filename(&self) -> String { + /// The path to the [`FileMetadata`] on disk. + /// + /// This will not point to the original file on disk, in the event that the file was saved + /// as a BNDB. When a BNDB is opened, the FileMetadata will contain the file path to the database. + /// + /// If you need the original binary file path, use [`FileMetadata::original_file_path`] instead. + /// + /// If you just want a name to present to the user, use [`FileMetadata::display_name`]. + pub fn file_path(&self) -> PathBuf { unsafe { let raw = BNGetFilename(self.handle); - BnString::into_string(raw) + PathBuf::from(BnString::into_string(raw)) } } - pub fn set_filename(&self, name: &str) { + // TODO: To prevent issues we will not allow users to set the file path as it really should be + // TODO: derived at construction and not modified later. + /// Set the files path on disk. + /// + /// This should always be a valid path. + pub(crate) fn set_file_path(&self, name: &Path) { let name = name.to_cstr(); - unsafe { BNSetFilename(self.handle, name.as_ptr()); } } - pub fn modified(&self) -> bool { + /// The display name of the file. Useful for presenting to the user. Can differ from the original + /// name of the file and can be overridden with [`FileMetadata::set_display_name`]. + pub fn display_name(&self) -> String { + let raw_name = unsafe { + let raw = BNGetDisplayName(self.handle); + BnString::into_string(raw) + }; + // Sometimes this display name may return a full path, which is not the intended purpose. + raw_name + .split('/') + .next_back() + .unwrap_or(&raw_name) + .to_string() + } + + /// Set the display name of the file. + /// + /// This can be anything and will not be used for any purpose other than presentation. + pub fn set_display_name(&self, name: &str) { + let name = name.to_cstr(); + unsafe { + BNSetDisplayName(self.handle, name.as_ptr()); + } + } + + /// The path to the original file on disk, if any. + /// + /// It may not be present if the BNDB was saved without it or cleared via [`FileMetadata::clear_original_file_path`]. + /// + /// Only prefer this over [`FileMetadata::file_path`] if you require the original binary location. + pub fn original_file_path(&self) -> Option<PathBuf> { + let raw_name = unsafe { + let raw = BNGetOriginalFilename(self.handle); + PathBuf::from(BnString::into_string(raw)) + }; + // If the original file path is empty, or the original file path is pointing to the same file + // as the database itself, we know the original file path does not exist. + if raw_name.as_os_str().is_empty() + || self.is_database_backed() && raw_name == self.file_path() + { + None + } else { + Some(raw_name) + } + } + + /// Set the original file path inside the database. Useful if it has since been cleared from the + /// database, or you have moved the original file. + pub fn set_original_file_path(&self, path: &Path) { + let name = path.to_cstr(); + unsafe { + BNSetOriginalFilename(self.handle, name.as_ptr()); + } + } + + /// Clear the original file path inside the database. This is useful since the original file path + /// may be sensitive information you wish to not share with others. + pub fn clear_original_file_path(&self) { + unsafe { + BNSetOriginalFilename(self.handle, std::ptr::null()); + } + } + + /// The non-filesystem path that describes how this file was derived from the container + /// transform system, detailing the sequence of transform steps and selection names. + /// + /// NOTE: Returns `None` if this [`FileMetadata`] was not processed by the transform system and + /// does not differ from that of the "physical" file path reported by [`FileMetadata::file_path`]. + pub fn virtual_path(&self) -> Option<String> { + unsafe { + let raw = BNGetVirtualPath(self.handle); + let path = BnString::into_string(raw); + // For whatever reason the core may report there being a virtual path as the file path. + // In the case where that occurs, we wish not to report there being one to the user. + match path.is_empty() || path == self.file_path() { + true => None, + false => Some(path), + } + } + } + + /// Sets the non-filesystem path that describes how this file was derived from the container + /// transform system. + pub fn set_virtual_path(&self, path: &str) { + let path = path.to_cstr(); + unsafe { + BNSetVirtualPath(self.handle, path.as_ptr()); + } + } + + pub fn is_modified(&self) -> bool { unsafe { BNIsFileModified(self.handle) } } @@ -372,9 +477,10 @@ impl FileMetadata { impl Debug for FileMetadata { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("FileMetadata") - .field("filename", &self.filename()) + .field("file_path", &self.file_path()) + .field("display_name", &self.display_name()) .field("session_id", &self.session_id()) - .field("modified", &self.modified()) + .field("is_modified", &self.is_modified()) .field("is_analysis_changed", &self.is_analysis_changed()) .field("current_view_type", &self.current_view()) .field("current_offset", &self.current_offset()) @@ -385,7 +491,7 @@ impl Debug for FileMetadata { impl Display for FileMetadata { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.filename()) + f.write_str(&self.display_name()) } } |
