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 /// A leaf-shaped human-readable name for UI presentation. Never contains a directory path.
180 /// Resolution order:
181 /// * An explicitly set display name (project-assigned, transform-synthesized for container
182 /// entries, or set via [`FileMetadata::set_display_name`]).
183 /// * Otherwise the leaf of [`FileMetadata::file_path`].
184 ///
185 /// Use this for tab titles, save-dialog default leaf names, logs, and any UI surface where
186 /// you'd refer to the file by name. Use [`FileMetadata::file_path`] for the physical path
187 /// that can be reopened.
188 pub fn display_name(&self) -> String {
189 let raw_name = unsafe {
190 let raw = BNGetDisplayName(self.handle);
191 BnString::into_string(raw)
192 };
193 // Sometimes this display name may return a full path, which is not the intended purpose.
194 raw_name
195 .split('/')
196 .next_back()
197 .unwrap_or(&raw_name)
198 .to_string()
199 }
200
201 /// Set the display name of the file.
202 ///
203 /// This can be anything and will not be used for any purpose other than presentation.
204 pub fn set_display_name(&self, name: &str) {
205 let name = name.to_cstr();
206 unsafe {
207 BNSetDisplayName(self.handle, name.as_ptr());
208 }
209 }
210
211 /// The path to the original file on disk, if any.
212 ///
213 /// It may not be present if the BNDB was saved without it or cleared via [`FileMetadata::clear_original_file_path`].
214 ///
215 /// If this [`FileMetadata`] is a database within a project, it may not have a "consumable" original
216 /// file path. Instead, this might return the path to the on disk file path of the project file that
217 /// this database was created from, for projects you should query through [`FileMetadata::project_file`].
218 ///
219 /// Only prefer this over [`FileMetadata::file_path`] if you require the original binary location.
220 pub fn original_file_path(&self) -> Option<PathBuf> {
221 let raw_name = unsafe {
222 let raw = BNGetOriginalFilename(self.handle);
223 PathBuf::from(BnString::into_string(raw))
224 };
225 // If the original file path is empty, or the original file path is pointing to the same file
226 // as the database itself, we know the original file path does not exist.
227 if raw_name.as_os_str().is_empty()
228 || self.is_database_backed() && raw_name == self.file_path()
229 {
230 None
231 } else {
232 Some(raw_name)
233 }
234 }
235
236 /// Set the original file path inside the database. Useful if it has since been cleared from the
237 /// database, or you have moved the original file.
238 pub fn set_original_file_path(&self, path: &Path) {
239 let name = path.to_cstr();
240 unsafe {
241 BNSetOriginalFilename(self.handle, name.as_ptr());
242 }
243 }
244
245 /// Clear the original file path inside the database. This is useful since the original file path
246 /// may be sensitive information you wish to not share with others.
247 pub fn clear_original_file_path(&self) {
248 unsafe {
249 BNSetOriginalFilename(self.handle, std::ptr::null());
250 }
251 }
252
253 /// The non-filesystem path describing how this file was derived from the container transform
254 /// system in the current session. There are three meaningful states:
255 /// * `None` - not yet processed by the transform system.
256 /// * `Some(p)` where `p == file_path()` - processed, no transform chain applied (plain file,
257 /// database, or the container system was disabled via `files.container.mode`).
258 /// * `Some(p)` where `p != file_path()` - derived container entry.
259 ///
260 /// Session-scoped: save-as does not persist the chain. Reopening the saved artifact yields
261 /// whatever chain that session's access path produces.
262 pub fn virtual_path(&self) -> Option<String> {
263 unsafe {
264 let raw = BNGetVirtualPath(self.handle);
265 let path = BnString::into_string(raw);
266 if path.is_empty() {
267 None
268 } else {
269 Some(path)
270 }
271 }
272 }
273
274 /// Sets the non-filesystem path that describes how this file was derived from the container
275 /// transform system.
276 pub fn set_virtual_path(&self, path: &str) {
277 let path = path.to_cstr();
278 unsafe {
279 BNSetVirtualPath(self.handle, path.as_ptr());
280 }
281 }
282
283 /// `true` if this file was produced by the container transform system, `false` for plain files,
284 /// databases, and FileMetadata that has not yet been processed by the transform system.
285 pub fn is_container_entry(&self) -> bool {
286 matches!(self.virtual_path(), Some(p) if p != self.file_path())
287 }
288
289 /// Whether the file is currently flagged as modified.
290 ///
291 /// When this returns `true`, the UI will prompt to save the database on close, as well as display
292 /// a dot in the files tab.
293 pub fn is_modified(&self) -> bool {
294 unsafe { BNIsFileModified(self.handle) }
295 }
296
297 /// Marks the file as modified such that we can prompt to save the database on close.
298 pub fn mark_modified(&self) {
299 unsafe {
300 BNMarkFileModified(self.handle);
301 }
302 }
303
304 /// Marks the file as saved such that [`FileMetadata::is_modified`] and [`FileMetadata::is_analysis_changed`]
305 /// will return `false` and the undo buffer associated with this [`FileMetadata`] will be updated.
306 pub fn mark_saved(&self) {
307 unsafe {
308 BNMarkFileSaved(self.handle);
309 }
310 }
311
312 pub fn is_analysis_changed(&self) -> bool {
313 unsafe { BNIsAnalysisChanged(self.handle) }
314 }
315
316 /// Checks to see if the database exists for the file.
317 pub fn is_database_backed(&self) -> bool {
318 // TODO: This seems to be a useless function. Replace with a call to file.database().is_some()?
319 self.is_database_backed_for_view_type("")
320 }
321
322 /// Checks to see if the file metadata has a [`Database`], and then checks to see if the `view_type`
323 /// is available.
324 ///
325 /// NOTE: Passing an empty string will simply check if the database exists.
326 pub fn is_database_backed_for_view_type(&self, view_type: &str) -> bool {
327 let view_type = view_type.to_cstr();
328 // TODO: This seems to be a useless function. Replace with a call to file.database().is_some()?
329 unsafe { BNIsBackedByDatabase(self.handle, view_type.as_ref().as_ptr() as *const _) }
330 }
331
332 /// Runs a failable function where the failure state will revert any undo actions that occurred
333 /// during the time of the function's execution.
334 ///
335 /// NOTE: This will commit or undo any actions that occurred on **any** thread as this state is not thread local.
336 ///
337 /// NOTE: This is **NOT** thread safe, if you are holding any locks that might be held by both the main thread
338 /// and the thread executing this function, you can deadlock. You should also never call this function
339 /// on multiple threads at a time. See the following issues:
340 /// - <https://github.com/Vector35/binaryninja-api/issues/6289>
341 /// - <https://github.com/Vector35/binaryninja-api/issues/6325>
342 pub fn run_undoable_transaction<F: FnOnce() -> Result<T, E>, T, E>(
343 &self,
344 func: F,
345 ) -> Result<T, E> {
346 let undo = self.begin_undo_actions(false);
347 let result = func();
348 match result {
349 Ok(t) => {
350 self.commit_undo_actions(&undo);
351 Ok(t)
352 }
353 Err(e) => {
354 self.revert_undo_actions(&undo);
355 Err(e)
356 }
357 }
358 }
359
360 /// Creates a new undo entry, any undo actions after this will be added to this entry.
361 ///
362 /// NOTE: This is **NOT** thread safe, if you are holding any locks that might be held by both the main thread
363 /// and the thread executing this function, you can deadlock. You should also never call this function
364 /// on multiple threads at a time. See the following issues:
365 /// - <https://github.com/Vector35/binaryninja-api/issues/6289>
366 /// - <https://github.com/Vector35/binaryninja-api/issues/6325>
367 pub fn begin_undo_actions(&self, anonymous_allowed: bool) -> String {
368 unsafe { BnString::into_string(BNBeginUndoActions(self.handle, anonymous_allowed)) }
369 }
370
371 /// Commits the undo entry with the id to the undo buffer.
372 ///
373 /// NOTE: This is **NOT** thread safe, if you are holding any locks that might be held by both the main thread
374 /// and the thread executing this function, you can deadlock. You should also never call this function
375 /// on multiple threads at a time. See the following issues:
376 /// - <https://github.com/Vector35/binaryninja-api/issues/6289>
377 /// - <https://github.com/Vector35/binaryninja-api/issues/6325>
378 pub fn commit_undo_actions(&self, id: &str) {
379 let id = id.to_cstr();
380 unsafe {
381 BNCommitUndoActions(self.handle, id.as_ref().as_ptr() as *const _);
382 }
383 }
384
385 /// Reverts the undo actions committed in the undo entry.
386 ///
387 /// NOTE: This is **NOT** thread safe, if you are holding any locks that might be held by both the main thread
388 /// and the thread executing this function, you can deadlock. You should also never call this function
389 /// on multiple threads at a time. See the following issues:
390 /// - <https://github.com/Vector35/binaryninja-api/issues/6289>
391 /// - <https://github.com/Vector35/binaryninja-api/issues/6325>
392 pub fn revert_undo_actions(&self, id: &str) {
393 let id = id.to_cstr();
394 unsafe {
395 BNRevertUndoActions(self.handle, id.as_ref().as_ptr() as *const _);
396 }
397 }
398
399 /// Forgets the undo actions committed in the undo entry.
400 ///
401 /// NOTE: This is **NOT** thread safe, if you are holding any locks that might be held by both the main thread
402 /// and the thread executing this function, you can deadlock. You should also never call this function
403 /// on multiple threads at a time. See the following issues:
404 /// - <https://github.com/Vector35/binaryninja-api/issues/6289>
405 /// - <https://github.com/Vector35/binaryninja-api/issues/6325>
406 pub fn forget_undo_actions(&self, id: &str) {
407 let id = id.to_cstr();
408 unsafe {
409 BNForgetUndoActions(self.handle, id.as_ref().as_ptr() as *const _);
410 }
411 }
412
413 pub fn undo(&self) {
414 unsafe {
415 BNUndo(self.handle);
416 }
417 }
418
419 pub fn redo(&self) {
420 unsafe {
421 BNRedo(self.handle);
422 }
423 }
424
425 /// Retrieve the raw view for the file, this should always be present.
426 ///
427 /// The "Raw" view is a special [`BinaryView`] that holds data required for updating and creating
428 /// [`Database`]s such as the view and load settings.
429 pub fn raw_view(&self) -> Ref<BinaryView> {
430 self.view_of_type("Raw")
431 .expect("Raw view should always be present")
432 }
433
434 /// The current view for the file.
435 ///
436 /// For example, opening a PE file and navigating to the linear view will return "Linear:PE".
437 pub fn current_view(&self) -> String {
438 unsafe { BnString::into_string(BNGetCurrentView(self.handle)) }
439 }
440
441 /// The current offset navigated to within the [`FileMetadata::current_view`].
442 pub fn current_offset(&self) -> u64 {
443 unsafe { BNGetCurrentOffset(self.handle) }
444 }
445
446 /// Navigate to an offset for a specific view.
447 ///
448 /// # Example
449 ///
450 /// ```no_run
451 /// use binaryninja::file_metadata::FileMetadata;
452 /// # let file: FileMetadata = unimplemented!();
453 /// file.navigate_to("Linear:Raw", 0x0).expect("Linear:Raw should always be present");
454 /// ```
455 pub fn navigate_to(&self, view: &str, offset: u64) -> Result<(), ()> {
456 let view = view.to_cstr();
457
458 unsafe {
459 if BNNavigate(self.handle, view.as_ref().as_ptr() as *const _, offset) {
460 Ok(())
461 } else {
462 Err(())
463 }
464 }
465 }
466
467 /// Get the [`BinaryView`] for the view type.
468 ///
469 /// # Example
470 ///
471 /// ```no_run
472 /// use binaryninja::file_metadata::FileMetadata;
473 /// # let file: FileMetadata = unimplemented!();
474 /// file.view_of_type("Raw").expect("Raw type should always be present");
475 /// ```
476 pub fn view_of_type(&self, view: &str) -> Option<Ref<BinaryView>> {
477 let view = view.to_cstr();
478
479 unsafe {
480 let raw_view_ptr = BNGetFileViewOfType(self.handle, view.as_ref().as_ptr() as *const _);
481 match raw_view_ptr.is_null() {
482 false => Some(BinaryView::ref_from_raw(raw_view_ptr)),
483 true => None,
484 }
485 }
486 }
487
488 /// The [`BinaryViewType`]s associated with this file.
489 ///
490 /// For example, opening a PE binary will have the following: "Raw", "PE".
491 ///
492 /// Because the type may not have been registered, and the actual [`BinaryViewType`] is not available,
493 /// we instead return the name of the view type.
494 pub fn view_types(&self) -> Array<BnString> {
495 let mut count = 0;
496 unsafe {
497 let types = BNGetExistingViews(self.handle, &mut count);
498 Array::new(types, count, ())
499 }
500 }
501
502 /// Get the [`ProjectFile`] for the [`FileMetadata`].
503 pub fn project_file(&self) -> Option<Ref<ProjectFile>> {
504 unsafe {
505 let res = NonNull::new(BNGetProjectFile(self.handle))?;
506 Some(ProjectFile::ref_from_raw(res))
507 }
508 }
509
510 /// Create a database for the file and its views at `file_path`.
511 ///
512 /// NOTE: Calling this while analysis is running will flag the next load of the database to
513 /// regenerate the current analysis.
514 pub fn create_database(&self, file_path: impl AsRef<Path>, settings: &SaveSettings) -> bool {
515 self.create_database_with_progress(file_path, settings, NoProgressCallback)
516 }
517
518 /// Create a database for the file and its views at `file_path`, with a progress callback.
519 ///
520 /// NOTE: Calling this while analysis is running will flag the next load of the database to
521 /// regenerate the current analysis.
522 pub fn create_database_with_progress<P: ProgressCallback>(
523 &self,
524 file_path: impl AsRef<Path>,
525 settings: &SaveSettings,
526 mut progress: P,
527 ) -> bool {
528 // Databases are created with the root view (Raw).
529 let raw_view = self.raw_view();
530 let file_path = file_path.as_ref().to_cstr();
531 unsafe {
532 BNCreateDatabaseWithProgress(
533 raw_view.handle,
534 file_path.as_ptr() as *mut _,
535 &mut progress as *mut P as *mut c_void,
536 Some(P::cb_progress_callback),
537 settings.handle,
538 )
539 }
540 }
541
542 /// Save a new snapshot of the current file.
543 ///
544 /// NOTE: Calling this while analysis is running will flag the next load of the database to
545 /// regenerate the current analysis.
546 pub fn save_auto_snapshot(&self) -> bool {
547 // Snapshots are saved with the root view (Raw).
548 let raw_view = self.raw_view();
549 unsafe { BNSaveAutoSnapshot(raw_view.handle, std::ptr::null_mut() as *mut _) }
550 }
551
552 // TODO: Deprecate this function? Does not seem to do anything different than `open_database`.
553 pub fn open_database_for_configuration(&self, file: &Path) -> Result<Ref<BinaryView>, ()> {
554 let file = file.to_cstr();
555 unsafe {
556 let bv =
557 BNOpenDatabaseForConfiguration(self.handle, file.as_ref().as_ptr() as *const _);
558
559 if bv.is_null() {
560 Err(())
561 } else {
562 Ok(BinaryView::ref_from_raw(bv))
563 }
564 }
565 }
566
567 // TODO: How this relates to `BNLoadFilename`?
568 pub fn open_database(&self, file: &Path) -> Result<Ref<BinaryView>, ()> {
569 let file = file.to_cstr();
570 let view = unsafe { BNOpenExistingDatabase(self.handle, file.as_ptr()) };
571
572 if view.is_null() {
573 Err(())
574 } else {
575 Ok(unsafe { BinaryView::ref_from_raw(view) })
576 }
577 }
578
579 // TODO: How this relates to `BNLoadFilename`?
580 pub fn open_database_with_progress<P: ProgressCallback>(
581 &self,
582 file: &Path,
583 mut progress: P,
584 ) -> Result<Ref<BinaryView>, ()> {
585 let file = file.to_cstr();
586
587 let view = unsafe {
588 BNOpenExistingDatabaseWithProgress(
589 self.handle,
590 file.as_ptr(),
591 &mut progress as *mut P as *mut c_void,
592 Some(P::cb_progress_callback),
593 )
594 };
595
596 if view.is_null() {
597 Err(())
598 } else {
599 Ok(unsafe { BinaryView::ref_from_raw(view) })
600 }
601 }
602
603 /// Get the database attached to this file.
604 ///
605 /// Only available if this file is a database, or [`FileMetadata::create_database`] has previously
606 /// been called on this file.
607 pub fn database(&self) -> Option<Ref<Database>> {
608 let result = unsafe { BNGetFileMetadataDatabase(self.handle) };
609 NonNull::new(result).map(|handle| unsafe { Database::ref_from_raw(handle) })
610 }
611}
612
613impl Debug for FileMetadata {
614 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
615 f.debug_struct("FileMetadata")
616 .field("file_path", &self.file_path())
617 .field("display_name", &self.display_name())
618 .field("session_id", &self.session_id())
619 .field("is_modified", &self.is_modified())
620 .field("is_analysis_changed", &self.is_analysis_changed())
621 .field("current_view_type", &self.current_view())
622 .field("current_offset", &self.current_offset())
623 .field("view_types", &self.view_types().to_vec())
624 .finish()
625 }
626}
627
628impl Display for FileMetadata {
629 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
630 f.write_str(&self.display_name())
631 }
632}
633
634impl PartialEq for FileMetadata {
635 fn eq(&self, other: &Self) -> bool {
636 self.session_id() == other.session_id()
637 }
638}
639
640impl Eq for FileMetadata {}
641
642impl Hash for FileMetadata {
643 fn hash<H: Hasher>(&self, state: &mut H) {
644 self.session_id().hash(state);
645 }
646}
647
648unsafe impl Send for FileMetadata {}
649unsafe impl Sync for FileMetadata {}
650
651impl ToOwned for FileMetadata {
652 type Owned = Ref<Self>;
653
654 fn to_owned(&self) -> Self::Owned {
655 unsafe { RefCountable::inc_ref(self) }
656 }
657}
658
659unsafe impl RefCountable for FileMetadata {
660 unsafe fn inc_ref(handle: &Self) -> Ref<Self> {
661 Ref::new(Self {
662 handle: BNNewFileReference(handle.handle),
663 })
664 }
665
666 unsafe fn dec_ref(handle: &Self) {
667 BNFreeFileMetadata(handle.handle);
668 }
669}