pub struct HfsPlusFilesystem<R: Read + Seek> { /* private fields */ }Expand description
HFS+ filesystem implementation.
Implementations§
Source§impl<R: Read + Seek> HfsPlusFilesystem<R>
impl<R: Read + Seek> HfsPlusFilesystem<R>
pub fn open(reader: R, partition_offset: u64) -> Result<Self, FilesystemError>
Sourcepub fn open_sized(
reader: R,
partition_offset: u64,
partition_size: Option<u64>,
) -> Result<Self, FilesystemError>
pub fn open_sized( reader: R, partition_offset: u64, partition_size: Option<u64>, ) -> Result<Self, FilesystemError>
open with the partition length, so the alternate volume
header is written where Apple’s tools look for it.
pub fn block_size(&self) -> u32
Sourcepub fn is_journaled(&self) -> bool
pub fn is_journaled(&self) -> bool
True when the volume header’s journaled bit is set.
Sourcepub fn journal_detail(
&mut self,
) -> Result<Option<JournalDetail>, FilesystemError>
pub fn journal_detail( &mut self, ) -> Result<Option<JournalDetail>, FilesystemError>
Decode the journal into per-transaction, per-block detail for the GUI
history viewer (Step 29). Block previews are capped; classification is
against [metadata_regions]. Ok(None) when not journaled.
pub fn read_journal(&mut self) -> Result<Option<JournalState>, FilesystemError>
Sourcepub fn snapshot(&self) -> HfsPlusSnapshot
pub fn snapshot(&self) -> HfsPlusSnapshot
Capture the full mutable state for snapshot/rollback. Cheap on the scale of a single mutation: catalog + bitmap clone is O(volume metadata size), not O(volume size).
Sourcepub fn restore_snapshot(&mut self, snap: HfsPlusSnapshot)
pub fn restore_snapshot(&mut self, snap: HfsPlusSnapshot)
Restore previously captured state. Used by the snapshot guard
around every EditableFilesystem mutation when the body returns
Err.
Sourcepub fn prepare_for_edit(&mut self) -> Result<(), FilesystemError>
pub fn prepare_for_edit(&mut self) -> Result<(), FilesystemError>
Sanity-check the volume for edit-mode compatibility and pre-load the
attributes-B-tree CNID set. Called by open_editable_filesystem —
not by read-only opens, so the cost is paid only when we’re about
to mutate.
Journaled volumes are accepted only when the journal is clean (empty,
start == end) — the cleanly-unmounted case. A clean journal has no
pending state, so editing the catalog in place and leaving the journal
empty keeps the volume valid for macOS (it replays nothing on mount).
A dirty journal carries metadata the catalog hasn’t absorbed yet;
editing on top of that stale catalog would lose the journaled changes,
so we still refuse it here. In-place transactional writes that journal
our own mutations and recover dirty volumes are the remaining half of
Step 27 (docs/hfsplus_enhancements.md) and are not yet wired in.
xattr-bearing volumes succeed but later delete_entry calls against any
of the cached CNIDs are refused at the source until Phase 5 lands.
Source§impl<R: Read + Write + Seek + Send> HfsPlusFilesystem<R>
impl<R: Read + Write + Seek + Send> HfsPlusFilesystem<R>
Sourcepub fn set_xattr(
&mut self,
cnid: u32,
name: &str,
value: &[u8],
) -> Result<(), FilesystemError>
pub fn set_xattr( &mut self, cnid: u32, name: &str, value: &[u8], ) -> Result<(), FilesystemError>
Set (or replace) an inline extended attribute on cnid.
Any existing record keyed by (cnid, name) is removed first so the
final state holds exactly one record per (cnid, name) pair. Records
whose payload would not fit in a single B-tree leaf node are rejected
with XattrTooLarge — fork-style storage is not yet wired up.
Wrapped in the snapshot guard so partial failures (e.g. a leaf split running out of free B-tree nodes) leave the in-memory state byte-equal to the pre-call state.
Sourcepub fn remove_xattr(
&mut self,
cnid: u32,
name: &str,
) -> Result<(), FilesystemError>
pub fn remove_xattr( &mut self, cnid: u32, name: &str, ) -> Result<(), FilesystemError>
Remove a single extended attribute by name. Returns NotFound if no
matching record exists. Wrapped in the snapshot guard.
Source§impl<R: Read + Write + Seek + Send> HfsPlusFilesystem<R>
impl<R: Read + Write + Seek + Send> HfsPlusFilesystem<R>
Sourcepub fn ensure_private_dir(&mut self) -> Result<FileEntry, FilesystemError>
pub fn ensure_private_dir(&mut self) -> Result<FileEntry, FilesystemError>
Locate the \0\0\0\0HFS+ Private Data directory at root, creating
it if missing. Returns its FileEntry. The directory’s existence is
the prerequisite for any hardlink-related write — both
Self::create_hardlink_inode and Self::create_hardlink call
this internally so the replay path doesn’t need to.
Sourcepub fn ensure_dir_private_dir(&mut self) -> Result<FileEntry, FilesystemError>
pub fn ensure_dir_private_dir(&mut self) -> Result<FileEntry, FilesystemError>
Locate the .HFS+ Private Directory Data\r directory at root,
creating it if missing. Companion to Self::ensure_private_dir
for directory hardlinks.
Sourcepub fn create_hardlink_inode(
&mut self,
inode_num: u32,
data: &mut dyn Read,
data_len: u64,
options: &CreateFileOptions,
) -> Result<FileEntry, FilesystemError>
pub fn create_hardlink_inode( &mut self, inode_num: u32, data: &mut dyn Read, data_len: u64, options: &CreateFileOptions, ) -> Result<FileEntry, FilesystemError>
Create a hardlink inode (iNode<inode_num>) under the volume’s
private directory. The inode’s data and resource forks hold the
shared payload; its bsdInfo.special (linkCount) starts at zero
and is bumped per-link by Self::create_hardlink. The caller is
responsible for calling sync_metadata once all replay edits are
staged.
Returns an AlreadyExists error if iNode<inode_num> already lives
in the private dir, so callers don’t accidentally double-emit.
Sourcepub fn create_hardlink(
&mut self,
parent: &FileEntry,
name: &str,
inode_num: u32,
) -> Result<FileEntry, FilesystemError>
pub fn create_hardlink( &mut self, parent: &FileEntry, name: &str, inode_num: u32, ) -> Result<FileEntry, FilesystemError>
Create a file hardlink stub at (parent, name) referencing the
inode iNode<inode_num> under the private dir. The stub carries
empty forks and FInfo ('hlnk', 'hfs+') with
bsdInfo.special = inode_num. The matching inode’s linkCount
(bsdInfo.special) is bumped by 1.
Returns the stub’s FileEntry with link_target_cnid set to the
inode’s CNID — callers that want the inode bytes can read through
it directly via Self::read_file / Self::write_file_to.
Sourcepub fn create_dir_hardlink_inode(
&mut self,
inode_num: u32,
options: &CreateDirectoryOptions,
) -> Result<FileEntry, FilesystemError>
pub fn create_dir_hardlink_inode( &mut self, inode_num: u32, options: &CreateDirectoryOptions, ) -> Result<FileEntry, FilesystemError>
Create a directory-hardlink inode (dir_<inode_num>) under the
volume’s directory-private directory. The inode is a regular folder
— callers populate its contents like any other directory. Its
bsdInfo.special (linkCount) starts at zero and is bumped per stub
by Self::create_dir_hardlink.
Sourcepub fn create_dir_hardlink(
&mut self,
parent: &FileEntry,
name: &str,
inode_num: u32,
) -> Result<FileEntry, FilesystemError>
pub fn create_dir_hardlink( &mut self, parent: &FileEntry, name: &str, inode_num: u32, ) -> Result<FileEntry, FilesystemError>
Create a directory-hardlink stub at (parent, name) referencing
dir_<inode_num> under the directory-private directory. The stub
is a FILE record (not a folder) with FInfo ('fdrp', 'MACS') and
bsdInfo.special = inode_num; list_directory surfaces it as a
Directory entry that follows the link automatically. The matching
inode’s linkCount (bsdInfo.special) is bumped by 1.
Trait Implementations§
Source§impl<R: Read + Write + Seek + Send> EditableFilesystem for HfsPlusFilesystem<R>
impl<R: Read + Write + Seek + Send> EditableFilesystem for HfsPlusFilesystem<R>
Source§fn as_filesystem(&self) -> &dyn Filesystem
fn as_filesystem(&self) -> &dyn Filesystem
Filesystem trait object. Read moreSource§fn as_filesystem_mut(&mut self) -> &mut dyn Filesystem
fn as_filesystem_mut(&mut self) -> &mut dyn Filesystem
as_filesystem.Source§fn create_file(
&mut self,
parent: &FileEntry,
name: &str,
data: &mut dyn Read,
data_len: u64,
options: &CreateFileOptions,
) -> Result<FileEntry, FilesystemError>
fn create_file( &mut self, parent: &FileEntry, name: &str, data: &mut dyn Read, data_len: u64, options: &CreateFileOptions, ) -> Result<FileEntry, FilesystemError>
Source§fn create_directory(
&mut self,
parent: &FileEntry,
name: &str,
options: &CreateDirectoryOptions,
) -> Result<FileEntry, FilesystemError>
fn create_directory( &mut self, parent: &FileEntry, name: &str, options: &CreateDirectoryOptions, ) -> Result<FileEntry, FilesystemError>
Source§fn delete_entry(
&mut self,
parent: &FileEntry,
entry: &FileEntry,
) -> Result<(), FilesystemError>
fn delete_entry( &mut self, parent: &FileEntry, entry: &FileEntry, ) -> Result<(), FilesystemError>
Source§fn rename(
&mut self,
parent: &FileEntry,
entry: &FileEntry,
new_name: &str,
) -> Result<(), FilesystemError>
fn rename( &mut self, parent: &FileEntry, entry: &FileEntry, new_name: &str, ) -> Result<(), FilesystemError>
new_name must validate via Filesystem::validate_name and
must not already name another entry in parent. Read moreSource§fn set_type_creator(
&mut self,
entry: &FileEntry,
type_code: &str,
creator_code: &str,
) -> Result<(), FilesystemError>
fn set_type_creator( &mut self, entry: &FileEntry, type_code: &str, creator_code: &str, ) -> Result<(), FilesystemError>
Unsupported so callers can branch on capability
rather than relying on silent no-ops.Source§fn write_resource_fork(
&mut self,
entry: &FileEntry,
data: &mut dyn Read,
len: u64,
) -> Result<(), FilesystemError>
fn write_resource_fork( &mut self, entry: &FileEntry, data: &mut dyn Read, len: u64, ) -> Result<(), FilesystemError>
Unsupported rather than silently dropping the fork.Source§fn set_permissions(
&mut self,
entry: &FileEntry,
mode: u32,
) -> Result<(), FilesystemError>
fn set_permissions( &mut self, entry: &FileEntry, mode: u32, ) -> Result<(), FilesystemError>
mode carries permission bits
only — implementations keep the inode’s file-type bits (see
[crate::fs::unix_common::inode::with_permission_bits]). Read moreSource§fn set_owner(
&mut self,
entry: &FileEntry,
uid: u32,
gid: u32,
) -> Result<(), FilesystemError>
fn set_owner( &mut self, entry: &FileEntry, uid: u32, gid: u32, ) -> Result<(), FilesystemError>
Source§fn sync_metadata(&mut self) -> Result<(), FilesystemError>
fn sync_metadata(&mut self) -> Result<(), FilesystemError>
Source§fn free_space(&mut self) -> Result<u64, FilesystemError>
fn free_space(&mut self) -> Result<u64, FilesystemError>
Source§fn set_blessed_folder(
&mut self,
entry: &FileEntry,
) -> Result<(), FilesystemError>
fn set_blessed_folder( &mut self, entry: &FileEntry, ) -> Result<(), FilesystemError>
Unsupported so the GUI can
gate the action and surface unexpected calls.Source§fn write_boot_blocks(
&mut self,
blocks: &[u8; 1024],
) -> Result<(), FilesystemError>
fn write_boot_blocks( &mut self, blocks: &[u8; 1024], ) -> Result<(), FilesystemError>
sync_metadata time. Used to make a
classic-HFS volume bootable by copying a donor’s boot blocks. Override
on HFS/HFS+ (the only filesystems with a Mac boot region); others
return Unsupported so the GUI can gate the action.Source§fn begin_bulk(&mut self)
fn begin_bulk(&mut self)
create_file), that bookkeeping dominates a large import. Read moreSource§fn end_bulk(&mut self)
fn end_bulk(&mut self)
begin_bulk). Default
is a no-op.Source§fn delete_recursive(
&mut self,
parent: &FileEntry,
entry: &FileEntry,
) -> Result<(), FilesystemError>
fn delete_recursive( &mut self, parent: &FileEntry, entry: &FileEntry, ) -> Result<(), FilesystemError>
Source§fn set_xattr(
&mut self,
_entry: &FileEntry,
_name: &str,
_value: &[u8],
) -> Result<(), FilesystemError>
fn set_xattr( &mut self, _entry: &FileEntry, _name: &str, _value: &[u8], ) -> Result<(), FilesystemError>
Unsupported. name must carry a valid namespace prefix
([crate::fs::xattr::has_valid_namespace]).Source§fn remove_xattr(
&mut self,
_entry: &FileEntry,
_name: &str,
) -> Result<(), FilesystemError>
fn remove_xattr( &mut self, _entry: &FileEntry, _name: &str, ) -> Result<(), FilesystemError>
Unsupported. Removing an absent attribute is
not an error.Source§fn set_prodos_type(
&mut self,
_entry: &FileEntry,
_type_byte: u8,
_aux_type: u16,
) -> Result<(), FilesystemError>
fn set_prodos_type( &mut self, _entry: &FileEntry, _type_byte: u8, _aux_type: u16, ) -> Result<(), FilesystemError>
Unsupported — override on ProDOS.Source§fn set_prodos_access(
&mut self,
_entry: &FileEntry,
_access: u8,
) -> Result<(), FilesystemError>
fn set_prodos_access( &mut self, _entry: &FileEntry, _access: u8, ) -> Result<(), FilesystemError>
Source§fn repair(&mut self) -> Result<RepairReport, FilesystemError>
fn repair(&mut self) -> Result<RepairReport, FilesystemError>
Source§fn set_volume_name(&mut self, _new_name: &str) -> Result<(), FilesystemError>
fn set_volume_name(&mut self, _new_name: &str) -> Result<(), FilesystemError>
Unsupported).Source§fn set_finder_info(
&mut self,
_entry: &FileEntry,
_finfo: [u8; 16],
_fxinfo: [u8; 16],
) -> Result<(), FilesystemError>
fn set_finder_info( &mut self, _entry: &FileEntry, _finfo: [u8; 16], _fxinfo: [u8; 16], ) -> Result<(), FilesystemError>
Unsupported. Used
by the MacBinary put path to apply Finder flags / location / folder
in one shot.Source§fn set_amiga_protection(
&mut self,
_entry: &FileEntry,
_protection: u32,
) -> Result<(), FilesystemError>
fn set_amiga_protection( &mut self, _entry: &FileEntry, _protection: u32, ) -> Result<(), FilesystemError>
Unsupported.
Set the DOS attribute bits on an entry: read-only 0x01, hidden 0x02,
system 0x04, archive 0x20. Read morefn set_dos_attributes( &mut self, _parent: &FileEntry, _entry: &FileEntry, _attrs: u16, ) -> Result<(), FilesystemError>
fn set_dates( &mut self, _entry: &FileEntry, _create: u32, _modify: u32, _backup: u32, ) -> Result<(), FilesystemError>
Source§fn supports_symlinks(&self) -> bool
fn supports_symlinks(&self) -> bool
Self::create_symlink is actually implemented). Default false;
override to true alongside create_symlink. Lets callers (e.g. the
tar-import preflight) warn before a lossy import that symlinks will
be dropped, rather than discovering it entry-by-entry.Source§fn stores_raw_names(&self) -> bool
fn stores_raw_names(&self) -> bool
crate::fs::raw_name] placeholders.
Only NeXT UFS volumes say yes; everywhere else a placeholder would land as junk.Source§fn create_symlink(
&mut self,
_parent: &FileEntry,
_name: &str,
_target: &str,
_options: &CreateFileOptions,
) -> Result<FileEntry, FilesystemError>
fn create_symlink( &mut self, _parent: &FileEntry, _name: &str, _target: &str, _options: &CreateFileOptions, ) -> Result<FileEntry, FilesystemError>
parent pointing at target (a path
string interpreted by the filesystem’s own resolver semantics).
Returns the new symlink’s entry. Default returns Unsupported —
override on filesystems that support symlinks (PFS3, AFFS,
ext, etc).Source§fn create_hardlink(
&mut self,
_parent: &FileEntry,
_name: &str,
_target: &FileEntry,
_options: &CreateFileOptions,
) -> Result<FileEntry, FilesystemError>
fn create_hardlink( &mut self, _parent: &FileEntry, _name: &str, _target: &FileEntry, _options: &CreateFileOptions, ) -> Result<FileEntry, FilesystemError>
parent pointing at an existing entry
already on this filesystem. target must be an entry returned
by a previous create_file / create_directory / list_directory
call on this same volume — its location identifies the inode /
anode / CNID that the new link references. Returns the new
hardlink’s entry. Default returns Unsupported — override on
filesystems that support hardlinks (PFS3, HFS+, ext, etc).Source§impl<R: Read + Seek + Send> Filesystem for HfsPlusFilesystem<R>
impl<R: Read + Seek + Send> Filesystem for HfsPlusFilesystem<R>
Source§fn allocation_unit(&self) -> Option<u64>
fn allocation_unit(&self) -> Option<u64>
HFS+ allocates fork storage in fixed-size allocation blocks
(blockSize), so a fork’s real on-disk footprint is its length
rounded up to this unit. Exposing it lets the copy preflight and
du model destination consumption exactly instead of falling back
to a 512-byte floor.
Source§fn list_directory(
&mut self,
entry: &FileEntry,
) -> Result<Vec<FileEntry>, FilesystemError>
fn list_directory( &mut self, entry: &FileEntry, ) -> Result<Vec<FileEntry>, FilesystemError>
Source§fn read_file(
&mut self,
entry: &FileEntry,
max_bytes: usize,
) -> Result<Vec<u8>, FilesystemError>
fn read_file( &mut self, entry: &FileEntry, max_bytes: usize, ) -> Result<Vec<u8>, FilesystemError>
max_bytes).Source§fn write_file_to(
&mut self,
entry: &FileEntry,
writer: &mut dyn Write,
) -> Result<u64, FilesystemError>
fn write_file_to( &mut self, entry: &FileEntry, writer: &mut dyn Write, ) -> Result<u64, FilesystemError>
Source§fn volume_label(&self) -> Option<&str>
fn volume_label(&self) -> Option<&str>
Source§fn validate_name(&self, name: &str) -> Result<(), FilesystemError>
fn validate_name(&self, name: &str) -> Result<(), FilesystemError>
name is legal for a new file or directory on this
filesystem. Returns Err(InvalidData) with a human-readable reason
when the name violates length, character, or encoding rules. Read moreSource§fn uses_colon_paths(&self) -> bool
fn uses_colon_paths(&self) -> bool
: as its native path separator
(classic Mac HFS / HFS+), so : can never appear in a catalog name. Read moreSource§fn total_size(&self) -> u64
fn total_size(&self) -> u64
Source§fn last_data_byte(&mut self) -> Result<u64, FilesystemError>
fn last_data_byte(&mut self) -> Result<u64, FilesystemError>
Source§fn defragmented_minimum_size(&mut self) -> Result<u64, FilesystemError>
fn defragmented_minimum_size(&mut self) -> Result<u64, FilesystemError>
Source§fn write_resource_fork_to(
&mut self,
entry: &FileEntry,
writer: &mut dyn Write,
) -> Result<u64, FilesystemError>
fn write_resource_fork_to( &mut self, entry: &FileEntry, writer: &mut dyn Write, ) -> Result<u64, FilesystemError>
Ok(0) (no resource fork).Source§fn resource_fork_size(&mut self, entry: &FileEntry) -> u64
fn resource_fork_size(&mut self, entry: &FileEntry) -> u64
0.Source§fn fragmentation_stats(
&mut self,
) -> Option<Result<FragmentationStats, FilesystemError>>
fn fragmentation_stats( &mut self, ) -> Option<Result<FragmentationStats, FilesystemError>>
None
when the filesystem can’t compute them (e.g. cluster-allocation
filesystems whose CompactReader already repacks during the backup
stream so “fragmentation” doesn’t translate into a backup-time
decision the user can make). Read moreSource§fn blessed_system_folder(&mut self) -> Option<(u64, String)>
fn blessed_system_folder(&mut self) -> Option<(u64, String)>
Source§fn fsck(&mut self) -> Option<Result<FsckResult, FilesystemError>>
fn fsck(&mut self) -> Option<Result<FsckResult, FilesystemError>>
None if not supported for
this filesystem type. Override in implementations that support fsck.Source§fn journal_detail(
&mut self,
) -> Result<Option<Box<dyn Any + Send>>, FilesystemError>
fn journal_detail( &mut self, ) -> Result<Option<Box<dyn Any + Send>>, FilesystemError>
Ok(None) if unjournaled.
The value is the filesystem’s own detail type (HFS+: JournalDetail), for the caller to downcast.Source§fn set_show_deleted(&mut self, _on: bool) -> bool
fn set_show_deleted(&mut self, _on: bool) -> bool
false when unsupported.Source§fn case_insensitive_lookup(&self) -> bool
fn case_insensitive_lookup(&self) -> bool
Source§fn supports_xattrs(&self) -> bool
fn supports_xattrs(&self) -> bool
user.*,
security.*, …). True on ext / XFS / SquashFS; false on filesystems with
no xattr concept (Minix, EFS, FAT, HFS). The UI uses this to show or hide
the xattr section rather than probing every file.Source§fn list_xattrs(
&mut self,
_entry: &FileEntry,
) -> Result<Vec<Xattr>, FilesystemError>
fn list_xattrs( &mut self, _entry: &FileEntry, ) -> Result<Vec<Xattr>, FilesystemError>
entry. Default: none. Override on
xattr-bearing filesystems. Returning an empty list is normal — most files
carry no xattrs even where the filesystem supports them.